有的时候会需要做一些自定义的动画效果,在会反复用到的动画效果可以考虑做成动画框架,方便使用,做成框架的话就需要考虑很多的问题,最典型的问题就是属性和方法必须要是可配置的,这里就来聊一聊自定义动画框架的做法

重难点分析

在自定义动画框架里面,最难的一个问题就是怎么样获得属性,如果直接写自定义的属性,那么编译时候就会报错了,那么自然就想到了在外层包裹自定义的属性,通过处理自定义的属性来得到,这样便是android源代码一些控件的解决办法,其实就是一招偷梁换柱,在处理的时候,如果是自定义的属性,就自己处理,如果不是自定义的属性,就交由系统处理,然后在addView上再做手脚,另外从xml的解析入手,可以自定义LayoutInflater,然后重写onCreateView(),在里面就可以完成偷梁换柱,故这里有两种方法可以完成

在addView时候拦截做法

这里做一个滑动的自定义动画

在滑动的View里面,需要实现滑动时候和重置的工作,这里有一个接口,及实现接口的方法来做

public interface DiscrollvableInterface {
//当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
void onDiscrollve(float ratio);
//重置view的属性----恢复view的原来属性
void onResetDiscrollve();
}

实现上面的接口

public class DiscrollvableView extends FrameLayout implements DiscrollvableInterface {

    private static final int TRANSLATION_FROM_TOP = 0x01;
private static final int TRANSLATION_FROM_BOTTOM = 0x02;
private static final int TRANSLATION_FROM_LEFT = 0x04;
private static final int TRANSLATION_FROM_RIGHT = 0x08; //颜色估值器
private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
//自定义属性的一些接收的变量
private int mDiscrollveFromBgColor;//背景颜色变化开始值
private int mDiscrollveToBgColor;//背景颜色变化结束值
private boolean mDiscrollveAlpha;//是否需要透明度动画
private int mDisCrollveTranslation;//平移值
private boolean mDiscrollveScaleX;//是否需要x轴方向缩放
private boolean mDiscrollveScaleY;//是否需要y轴方向缩放
private int mHeight;//本view的高度
private int mWidth;//宽度 public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) {
this.mDiscrollveFromBgColor = mDiscrollveFromBgColor;
} public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) {
this.mDiscrollveToBgColor = mDiscrollveToBgColor;
} public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) {
this.mDiscrollveAlpha = mDiscrollveAlpha;
} public void setmDisCrollveTranslation(int mDisCrollveTranslation) {
this.mDisCrollveTranslation = mDisCrollveTranslation;
} public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) {
this.mDiscrollveScaleX = mDiscrollveScaleX;
} public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) {
this.mDiscrollveScaleY = mDiscrollveScaleY;
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
onResetDiscrollve();
} public DiscrollvableView(Context context, AttributeSet attrs) {
super(context, attrs);
} public DiscrollvableView(Context context) {
super(context);
} @Override
public void onDiscrollve(float ratio) {
// ratio:0~1
//控制自身的动画属性
if (mDiscrollveAlpha) {
setAlpha(ratio);
}
if (mDiscrollveScaleX) {
setScaleX(ratio);
}
if (mDiscrollveScaleY) {
setScaleY(ratio);
}
//判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
if (isDiscrollTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight * (1 - ratio));//mHeight-->0(代表原来的位置)
}
if (isDiscrollTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight * (1 - ratio));//-mHeight-->0(代表原来的位置)
}
if (isDiscrollTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth * (1 - ratio));//-width-->0(代表原来的位置)
}
if (isDiscrollTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth * (1 - ratio));//width-->0(代表原来的位置)
}
//颜色渐变动画
if (mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
//ratio=0.5 color=中间颜色
setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
} } private boolean isDiscrollTranslationFrom(int translationMask) {
if (mDisCrollveTranslation == -1) {
return false;
}
//fromLeft|fromBottom & fromBottom = fromBottom
return (mDisCrollveTranslation & translationMask) == translationMask;
} @Override
public void onResetDiscrollve() {
//控制自身的动画属性
if (mDiscrollveAlpha) {
setAlpha(0);
}
if (mDiscrollveScaleX) {
setScaleX(0);
}
if (mDiscrollveScaleY) {
setScaleY(0);
}
//判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
if (isDiscrollTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight);//mHeight-->0(代表原来的位置)
}
if (isDiscrollTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight);//-mHeight-->0(代表原来的位置)
}
if (isDiscrollTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth);//-width-->0(代表原来的位置)
}
if (isDiscrollTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth);//width-->0(代表原来的位置)
}
}
}

那么便完成了对于滑动效果的控制,为了便于控制,自定义View继承ScrollView

public class DiscrollView extends ScrollView {

    private DiscrollViewContent mContent;

    public DiscrollView(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
protected void onFinishInflate() {
super.onFinishInflate();
View content = getChildAt(0);
mContent = (DiscrollViewContent) content;
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
View first = mContent.getChildAt(0);
first.getLayoutParams().height = getHeight();
} @Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt); int scrollViewHeight = getHeight();
//监听滑动----接口---->控制DiscrollViewContent的属性
//遍历MyLinearLayout里面所有子控件(MyViewGroup)
for (int i = 0; i < mContent.getChildCount(); i++) {
View child = mContent.getChildAt(i);
if (!(child instanceof DiscrollvableInterface)) {
continue;
} //ratio:0~1
DiscrollvableInterface discrollvableInterface = (DiscrollvableInterface) child;
//1.child离scrollview顶部的高度
int discrollvableTop = child.getTop();
int discrollvableHeight = child.getHeight(); //2.得到scrollview滑出去的高度
//3.得到child离屏幕顶部的高度
int discrollvableAbsoluteTop = discrollvableTop - t;
//什么时候执行动画?当child滑进屏幕的时候
if (discrollvableAbsoluteTop <= scrollViewHeight) {
int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
//确保ratio是在0~1,超过了1 也设置为1
discrollvableInterface.onDiscrollve(clamp(visibleGap / (float) discrollvableHeight, 1f, 0f));
} else {//否则,就恢复到原来的位置
discrollvableInterface.onResetDiscrollve();
}
}
} public static float clamp(float value, float max, float min) {
return Math.max(Math.min(value, max), min);
}
}

设置属性,偷梁换柱

public class DiscrollViewContent extends LinearLayout {

    public DiscrollViewContent(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
} @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
// 得到xml里面穿过来的参数
return new MyLayoutParams(getContext(), attrs);
} @Override
public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
//从child里面拿到我自定义的属性,传到discrollvableView里面
MyLayoutParams p = (MyLayoutParams) params;
if (!isDiscrollvable(p)) {//判断该view是否穿了自定义属性值,不是就不需要执行动画,不包一层FrameLayout
super.addView(child, index, params);
} else {
//在addView里面插一脚,往child外面包裹一层FrameLayout
DiscrollvableView discrollvableView = new DiscrollvableView(getContext());
discrollvableView.setmDiscrollveAlpha(p.mDiscrollveAlpha);
discrollvableView.setmDisCrollveTranslation(p.mDisCrollveTranslation);
discrollvableView.setmDiscrollveScaleX(p.mDiscrollveScaleX);
discrollvableView.setmDiscrollveScaleY(p.mDiscrollveScaleY);
discrollvableView.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
discrollvableView.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);
discrollvableView.addView(child);
super.addView(discrollvableView, index, params);
}
} private boolean isDiscrollvable(MyLayoutParams p) {
return p.mDiscrollveAlpha || p.mDiscrollveScaleX
|| p.mDiscrollveScaleY || p.mDisCrollveTranslation != -1
|| (p.mDiscrollveFromBgColor != -1 && p.mDiscrollveToBgColor != -1);
} public static class MyLayoutParams extends LinearLayout.LayoutParams {
public int mDiscrollveFromBgColor;//背景颜色变化开始值
public int mDiscrollveToBgColor;//背景颜色变化结束值
public boolean mDiscrollveAlpha;//是否需要透明度动画
public int mDisCrollveTranslation;//平移值
public boolean mDiscrollveScaleX;//是否需要x轴方向缩放
public boolean mDiscrollveScaleY;//是否需要y轴方向缩放
public MyLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
// 从child里面拿到我自定义的属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);
mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
mDisCrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
a.recycle();
}
}
}

最后是一些自定义属性

<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="DiscrollView_LayoutParams">
<attr name="discrollve_alpha" format="boolean"/>
<attr name="discrollve_scaleX" format="boolean"/>
<attr name="discrollve_scaleY" format="boolean"/>
<attr name="discrollve_fromBgColor" format="color"/>
<attr name="discrollve_toBgColor" format="color"/>
<attr name="discrollve_translation"/>
</declare-styleable> <attr name="discrollve_translation">
<flag name="fromTop" value="0x01" />
<flag name="fromBottom" value="0x02" />
<flag name="fromLeft" value="0x04" />
<flag name="fromRight" value="0x08" />
</attr>
</resources>

布局

<com.cj5785.customanimationaddview.DiscrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"> <com.cj5785.customanimationaddview.DiscrollViewContent
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:layout_width="match_parent"
android:layout_height="560dp"
android:background="@android:color/white"
android:gravity="center"
android:text="测试文字使用情况"
android:textSize="25sp" /> <View
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@android:color/darker_gray"
custom:discrollve_alpha="true" /> <ImageView
android:layout_width="89dp"
android:layout_height="80dp"
android:src="@drawable/duck"
custom:discrollve_alpha="true"
custom:discrollve_translation="fromLeft|fromBottom" /> <View
android:layout_width="match_parent"
android:layout_height="200dp"
custom:discrollve_fromBgColor="#ffff00"
custom:discrollve_toBgColor="#88EE66" /> <ImageView
android:layout_width="150dp"
android:layout_height="106dp"
android:layout_gravity="right"
android:src="@drawable/camera"
custom:discrollve_translation="fromRight" /> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="第二段文字使用测试"
android:textSize="28sp"
custom:discrollve_alpha="true"
custom:discrollve_translation="fromBottom" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:src="@drawable/sun"
custom:discrollve_scaleX="true"
custom:discrollve_scaleY="true" /> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:src="@drawable/balloon"
custom:discrollve_translation="fromLeft|fromBottom" /> </com.cj5785.customanimationaddview.DiscrollViewContent> </com.cj5785.customanimationaddview.DiscrollView>

效果如下

自定义LayoutInflater

这里实现一个仿小红书的视差动画效果

首先定义一个根布局,然后实例化其自定义控件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"> <com.cj5785.customanimationlayoutinflater.ParallaxContainer
android:id="@+id/parallax_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/> <ImageView
android:id="@+id/iv_man"
android:layout_width="66dp"
android:layout_height="202dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:background="@drawable/intro_item_manrun_1"/> </RelativeLayout>

实例化自定义控件,在最后一个登陆界面,由于与前面的不同,使用切换Fragment的方式,需要使用到适配器

适配器

public class ParallaxAdapter extends FragmentPagerAdapter {
private List<ParallaxFragment> fragmentList; public ParallaxAdapter(FragmentManager fm, List<ParallaxFragment> fragmentList) {
super(fm);
this.fragmentList = fragmentList;
} @Override
public Fragment getItem(int position) {
return fragmentList.get(position);
} @Override
public int getCount() {
return fragmentList.size();
}
}

自定义Fragment,其主要的偷梁换柱工作就是在这里完成的,返回我们处理以后的View

public class ParallaxFragment extends Fragment {
private List<View> parallaxViews = new ArrayList<>(); @Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Bundle bundle = getArguments();
int layoutId = bundle.getInt("layoutId");
//正常情况下在这里获得view,然后返回,我们就在这里偷梁换柱
// View view = inflater.inflate(layoutId, container);
//使用自定义的渲染器渲染
ParallaxLayoutInflater layoutInflater = new ParallaxLayoutInflater(inflater, getActivity(), this); return layoutInflater.inflate(layoutId, null);
} public List<View> getParallaxViews() {
return parallaxViews;
}
}

动画引导层

//引导页的最外层布局
public class ParallaxContainer extends FrameLayout implements OnPageChangeListener {
private List<ParallaxFragment> fragmentList;
private ParallaxAdapter adapter;
private float containerWidth;
private ImageView iv_man; public ParallaxContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
} //指定引导页的所有页面布局文件
public void setUp(int... ids) {
//初始化framentList,在调用处直接调用setUp()
fragmentList = new ArrayList<>();
for (int i = 0; i < ids.length; i++) {
ParallaxFragment fragment = new ParallaxFragment();
Bundle bundle = new Bundle();
//Fragment中需要加载的布局文件id
bundle.putInt("layoutId", ids[i]);
fragment.setArguments(bundle);
fragmentList.add(fragment);
}
//设置ViewPager
ViewPager viewPager = new ViewPager(getContext());
viewPager.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
viewPager.setId(R.id.parallax_pager);
adapter = new ParallaxAdapter(((MainActivity) getContext()).getSupportFragmentManager(), fragmentList);
viewPager.setAdapter(adapter);
addView(viewPager, 0);
//设置监听
viewPager.addOnPageChangeListener(this);
} @Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
containerWidth = getWidth();
//在翻页的过程中,不断根据视图的标签中对应的动画参数,改变视图的位置或者透明度
//获取到进入的页面
ParallaxFragment inFragment = null;
try {
inFragment = fragmentList.get(position - 1);
} catch (Exception e) {
}
//获取到退出的页面
ParallaxFragment outFragment = null;
try {
outFragment = fragmentList.get(position);
} catch (Exception e) {
} if (inFragment != null) {
//获取Fragment上所有的视图,实现动画效果
List<View> inViews = inFragment.getParallaxViews();
if (inViews != null) {
for (View view : inViews) {
//获取标签,从标签上获取所有的动画参数
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null) {
continue;
}
//translationY改变view的偏移位置,translationY=100,代表view在其原始位置向下移动100
//仔细观察进入的fragment中view从远处过来,不断向下移动,最终停在原始位置
view.setTranslationX((containerWidth - positionOffsetPixels) * tag.xIn);
view.setTranslationY((containerWidth - positionOffsetPixels) * tag.yIn);
}
}
} if (outFragment != null) {
List<View> outViews = outFragment.getParallaxViews();
if (outViews != null) {
for (View view : outViews) {
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null) {
continue;
}
//仔细观察退出的fragment中view从原始位置开始向上移动,translationY应为负数
view.setTranslationX((-positionOffsetPixels) * tag.xOut);
view.setTranslationY((-positionOffsetPixels) * tag.yOut);
}
}
}
} @Override
public void onPageSelected(int position) {
if (position == adapter.getCount() - 1) {
iv_man.setVisibility(INVISIBLE);
} else {
iv_man.setVisibility(VISIBLE);
}
} @Override
public void onPageScrollStateChanged(int state) {
AnimationDrawable animation = (AnimationDrawable) iv_man.getBackground();
switch (state) {
case ViewPager.SCROLL_STATE_DRAGGING:
animation.start();
break;
case ViewPager.SCROLL_STATE_IDLE:
animation.stop();
break;
default:
break;
}
} public void setIv_man(ImageView iv_man) {
this.iv_man = iv_man;
}
}

拦截系统处理xml

public class ParallaxLayoutInflater extends LayoutInflater {

    private static final String TAG = "ParallaxLayoutInflater";
private ParallaxFragment parallaxFragment; protected ParallaxLayoutInflater(LayoutInflater original, Context newContext, ParallaxFragment parallaxFragment) {
super(original, newContext);
this.parallaxFragment = parallaxFragment;
//源代码中存在:
//View view;
//if (mFactory2 != null) {
// view = mFactory2.onCreateView(parent, name, context, attrs);
//} else if (mFactory != null) {
// view = mFactory.onCreateView(name, context, attrs);
//} else {
// view = null;
//}
//if (view == null && mPrivateFactory != null) {
// view = mPrivateFactory.onCreateView(parent, name, context, attrs);
//}
//也就是说只要实现Factory,那么就不会调用后面的方法
setFactory(new ParallaxFactory(this));
} @Override
public LayoutInflater cloneInContext(Context newContext) {
//实际用于创建LayoutInflater的方法
return new ParallaxLayoutInflater(this, newContext, parallaxFragment);
} //这个框架最核心的地方就是在这里设置Factory,从而进行拦截
class ParallaxFactory implements LayoutInflater.Factory{ private LayoutInflater inflater;
//系统提供的视图分为android.widget.xxx和android.view.xxx
private final String[] prefixs = {
"android.widget.",
"android.view."
}; public ParallaxFactory(LayoutInflater inflater) {
this.inflater = inflater;
} @Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
//实例化view
View view = null;
if (view == null) {
view = createViewOrFailQuietly(name,context,attrs);
}
if (view != null) {
//获取自定义属性,并将自定义标签值绑定到view上面
getCustomAttrs(context, attrs, view);
parallaxFragment.getParallaxViews().add(view);
}
return view;
} private void getCustomAttrs(Context context, AttributeSet attrs, View view) {
//所有自定义的属性
int[] attrIds = {
R.attr.a_in,
R.attr.a_out,
R.attr.x_in,
R.attr.x_out,
R.attr.y_in,
R.attr.y_out
};
//获取
TypedArray typedArray = context.obtainStyledAttributes(attrs, attrIds);
if (typedArray != null && typedArray.length() > 0) {
//获取自定义属性的值
Log.d(TAG, "getCustomAttrs");
ParallaxViewTag tag = new ParallaxViewTag();
tag.alphaIn = typedArray.getFloat(0, 0f);
tag.alphaOut = typedArray.getFloat(1, 0f);
tag.xIn = typedArray.getFloat(2, 0f);
tag.xOut = typedArray.getFloat(3, 0f);
tag.yIn = typedArray.getFloat(4, 0f);
tag.yOut = typedArray.getFloat(5, 0f);
view.setTag(R.id.parallax_view_tag, tag);
}
typedArray.recycle();
} private View createViewOrFailQuietly(String name, String prefix, Context context, AttributeSet attrs) {
try {
//通过系统的inflater创建视图,读取系统的属性
return inflater.createView(name, prefix, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
} private View createViewOrFailQuietly(String name, Context context, AttributeSet attrs) {
//通过系统inflater创建视图
//1.自定义控件标签名称带点,所以创建时不需要前缀
if (name.contains(".")) {
return createViewOrFailQuietly(name, null, context, attrs);
}
//2.系统视图需要加上前缀
for (String prefix : prefixs) {
View view = createViewOrFailQuietly(name, prefix, context, attrs);
if (view != null) {
return view;
}
}
return null;
}
}
}

参数控制

//视差动画播放时参数的控制
public class ParallaxViewTag {
protected int index;
protected float xIn;
protected float xOut;
protected float yIn;
protected float yOut;
protected float alphaIn;
protected float alphaOut; @Override
public String toString() {
return "ParallaxViewTag [index=" + index + ", xIn=" + xIn + ", xOut=" + xOut + ", yIn=" + yIn
+ ", yOut=" + yOut + ", alphaIn=" + alphaIn + ", alphaOut=" + alphaOut + "]";
}
}

同时完成人行走的帧动画

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/intro_item_manrun_1"
android:duration="200" />
<item
android:drawable="@drawable/intro_item_manrun_2"
android:duration="200" />
</animation-list>

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="a_in" format="float" />
<attr name="a_out" format="float" />
<attr name="x_in" format="float" />
<attr name="x_out" format="float" />
<attr name="y_in" format="float" />
<attr name="y_out" format="float" />
</resources>

ids

<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="parallax_pager" type="id"/>
<item name="parallax_view_tag" type="id"/>
</resources>

实现效果如下图所示

最新文章

  1. Lucene.net站内搜索—3、最简单搜索引擎代码
  2. 配置163Yum源自动判断你的系统是Centos版本(适用于5.x或6.x)
  3. Binary Tree Vertical Order Traversal
  4. 实习小记-python中可哈希对象是个啥?what is hashable object in python?
  5. phpcms v9 模板调用代码大全
  6. Ext中 get、getDom、getCmp的区别
  7. http://acm.hdu.edu.cn/showproblem.php?pid=2579
  8. 如何将std::string转int,double? (C/C++) (C) (template)
  9. 查找mysql数据库文件的存放位置
  10. java学习第21天(IO流的使用)
  11. 学习Acegi应用到实际项目中(10)- 保护业务方法
  12. CSS小知识-----前缀-moz-、-ms-、-webkit-,-o-分别代表的意思
  13. 20165228 实验一 Java开发环境的熟悉
  14. Django中的视图
  15. 在Centos7下发布.NET CORE项目[转]
  16. 01 Getting Started 开始
  17. ny2 括号配对问题
  18. [R语言统计]频数表
  19. django 用户注册功能实现
  20. iOS 开发经验总结

热门文章

  1. LIO -SCSI target
  2. P4921 【情侣?给我烧了!】
  3. Xilinx ISE的时序约束
  4. 4、http协议之二
  5. C# 常用日期取得
  6. [loj 2478][luogu P4843]「九省联考 2018」林克卡特树
  7. docker中部署django项目~~Dockfile方式和compose方式
  8. python3.x中lambda表达式的处理与python2不一样
  9. 浅析TCP三次握手及四次挥手
  10. leaflet地图框架