高级UI之自定义动画框架

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

重难点分析

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

在addView时候拦截做法

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

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

实现上面的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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);
}
}

设置属性,偷梁换柱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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();
}
}
}

最后是一些自定义属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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>

布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<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

这里实现一个仿小红书的视差动画效果
首先定义一个根布局,然后实例化其自定义控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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的方式,需要使用到适配器
适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}
}

动画引导层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//引导页的最外层布局
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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;
}
}
}

参数控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//视差动画播放时参数的控制
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 + "]";
}
}

同时完成人行走的帧动画

1
2
3
4
5
6
7
8
9
10
<?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>

自定义属性

1
2
3
4
5
6
7
8
9
<?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

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

实现效果如下图所示

Donate comment here