您的位置:首页 > 其它

(二十四)自定义动画框架

2017-10-04 10:25 253 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、效果



这边代码很简单,主要学习这个动画框架开发过程的思想,开发出来的动画框架便于使用,利于扩展。

二、分析

实现滚动是使用了 ScrollView,如果说要使用 ListView 的话,理论上也是可以的,但是 Item 类型比较多的时候,估计会比较复杂。

ScrollView 下,里面的每个 Item 可以有一些动画效果,支持的动画有四种:

1.透明度变化

2.X 或 Y 方向缩放

3.颜色渐变

4.平移进场

通过监听 ScrollView 的滑动,调用对应 Item 的设置属性方法。Item 执行动画的程度跟这个 Item 从底部滑出来的高度有关,需要先计算 Item 滑出来多少。

每个 Item 执行的动画不一致,这边把 Item 要执行什么动画作为自定义属性配置在各自的 Item 上面,这些 Item 是系统自带的 View,如 TextView、ImageView 等。动画由 MyFrameLayout 去控制。

<LinearLayout>
<MyFrameLayout
discrollve:discrollve_scaleY="true"
discrollve:discrollve_translation="fromLeft" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_gravity="center"
android:src="@drawable/camera"
/>
</MyFrameLayout>
</LinearLayout>


三、包裹 Item

1.MyFrameLayout

先定义个 MyFrameLayout 包裹类,这个类包裹每一个 Item,并控制动画的执行。

public class MyFrameLayout extends FrameLayout {

public MyFrameLayout(@NonNull Context context) {
super(context);
}
}


2.MyLinearLayout

考虑到再 XML 布局文件中, LinearLayout 下每一个 Item 都需要在外添加一个 MyFrameLayout 才可以,这样使用起来较为麻烦,所以 自定义 MyLinearLayout 扩展自 LinearLayout,默认为子 View 添加一个 MyFrameLayout。

public class MyLinearLayout extends LinearLayout {

public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//设置排版为竖着
setOrientation(VERTICAL);
}

@Override
public void addView(View child, ViewGroup.LayoutParams params) {

MyFrameLayout mf = new MyFrameLayout(getContext());
mf.addView(child);
super.addView(mf, params);
}
}


这时候布局文件样式为:

<MyLinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_gravity="center"
android:src="@drawable/camera"
discrollve:discrollve_scaleY="true"
discrollve:discrollve_translation="fromLeft"
/>
</MyLinearLayout>


四、自定义属性

自定义属性 attrs.xml

<?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>


上面为了方便使用,扩展了 LinearLayout,让他自动为子 View 添加一个 MyFrameLayout,这时候自定义属性只能写在各个 Item 上,但是 Item 是系统的 View,自身无法识别到这些属性,所以是让 MyLinearLayout 去识别子 View 身上的属性。即重写 generateLayoutParams 方法。

1.自定义 LayoutParams

public 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 c, AttributeSet attrs) {
super(c, attrs);
//解析attrs得到自定义的属性,保存
TypedArray a = getContext().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();
}
}


同时,重写 MyLinearLayout 的 generateLayoutParams 方法。

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyLayoutParams(getContext(),attrs);
}


2.MyFrameLayout 保存自定义属性

为 MyFrameLayout 添加自定义属性,生成 set 方法。同时,重写 onSizeChanged 方法,记录宽高。

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) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}


在 MyLinearLayout 的 addView 方法中,把自定义属性保存到 MyFrameLayout 中。添加了一个判断是否有要执行的自定义动画,没有的话则不进行包裹。性能上的一点优化。

@Override
public void addView(View child, ViewGroup.LayoutParams params) {

MyLayoutParams p = (MyLayoutParams) params;
if(!isDiscrollvable(p)){//判断是否有自定义属性,没有则不包裹一层容器
super.addView(child,params);
}else {
//偷天换日
MyFrameLayout mf = new MyFrameLayout(getContext());
mf.addView(child);
mf.setmDiscrollveAlpha(p.mDiscrollveAlpha);
mf.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
mf.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);
mf.setmDiscrollveScaleX(p.mDiscrollveScaleX);
mf.setmDisCrollveTranslation(p.mDisCrollveTranslation);
super.addView(mf, params);
}
}

/**
* 是否要执行自定义动画
* @param p
* @return
*/
private boolean isDiscrollvable(MyLayoutParams p){
return p.mDiscrollveAlpha||
p.mDiscrollveScaleX||
p.mDiscrollveScaleY||
p.mDisCrollveTranslation!=-1||
(p.mDiscrollveFromBgColor!=-1&&
p.mDiscrollveToBgColor!=-1);
}


五、动画

1.封装动画方法

在上面已经把要执行的动画属性传给了 MyFrameLayout,为 MyFrameLayout 实现两个方法,设置属性值和初始化。

为了便于扩展,这边采用接口。

接口 DiscrollInterface:

public interface DiscrollInterface {
/**
* 当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
* @param ratio 动画执行的百分比(child view画出来的距离百分比)
*/
void onDiscroll(float ratio);

/**
* 重置动画--让view所有的属性都恢复到原来的样子
*/
void onResetDiscroll();
}


MyFrameLayout 实现 DiscrollInterface:

public class MyFrameLayout extends FrameLayout implements DiscrollInterface{
...

@Override
public void onDiscroll(float ratio) {
//执行动画ratio:0~1
if(mDiscrollveAlpha){
setAlpha(ratio);
}
if(mDiscrollveScaleX){
setScaleX(ratio);
}
if(mDiscrollveScaleY){
setScaleY(ratio);
}
//平移动画  int值:left,right,top,bottom    left|bottom
if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//是否包含bottom
setTranslationY(mHeight*(1-ratio));//height--->0(0代表恢复到原来的位置)
}
if(isTranslationFrom(TRANSLATION_FROM_TOP)){//是否包含bottom
setTranslationY(-mHeight*(1-ratio));//-height--->0(0代表恢复到原来的位置)
}
if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0代表恢复到本来原来的位置)
}
if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
setTranslationX(mWidth*(1-ratio));//-mWidth--->0(0代表恢复到本来原来的位置)
}
//判断从什么颜色到什么颜色
if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){
setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
}
}

private boolean isTranslationFrom(int translationMask){
if(mDisCrollveTranslation ==-1){
return false;
}
//fromLeft|fromeBottom & fromBottom = fromBottom
return (mDisCrollveTranslation & translationMask) == translationMask;
}

@Override
public void onResetDiscroll() {
if(mDiscrollveAlpha){
setAlpha(1);
}
if(mDiscrollveScaleX){
setScaleX(1);
}
if(mDiscrollveScaleY){
setScaleY(1);
}
//平移动画  int值:left,right,top,bottom    left|bottom
if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//是否包含bottom
setTranslationY(0);//height--->0(0代表恢复到原来的位置)
}
if(isTranslationFrom(TRANSLATION_FROM_TOP)){//是否包含bottom
setTranslationY(0);//-height--->0(0代表恢复到原来的位置)
}
if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
setTranslationX(0);//mWidth--->0(0代表恢复到本来原来的位置)
}
if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
setTranslationX(0);//-mWidth--->0(0代表恢复到本来原来的位置)
}
}
}


六、ScrollView 监听

到这里,就缺一个滑动时候对执行动画的监听,为了方便使用,扩展 ScrollView ,实现滑动监听执行动画效果。

这里比较复杂的就是计算最后一个 Item 滑出来的距离,从而确认执行动画的百分比 ratio。ratio = child 浮现的高度/ child 的高度,浮现的高度没有办法直接获取,只能用 ScrollView 的高度减去 Child 离 ScrollView 的顶部距离(红色箭头距离)再减去 ScrollView 滑出去的距离(绿色部分)。



public class MyScrollView extends ScrollView {

private MyLinearLayout mContent;

public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = (MyLinearLayout) getChildAt(0);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//为了好看,让第一个子 View 占满
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();

for (int i=0;i<mContent.getChildCount();i++){
View child = mContent.getChildAt(i);
int childHeight = child.getHeight();

if(!(child instanceof DiscrollInterface)){
continue;
}
DiscrollInterface discrollInterface = (DiscrollInterface) child;
//child离parent顶部的高度
int childTop = child.getTop();
//滑出去的这一截高度:t (t 为负的)
//child离屏幕顶部的高度
int absoluteTop = childTop - t;
if(absoluteTop <= scrollViewHeight) {
//child浮现的高度 = ScrollView 的高度 - child 离屏幕顶部的高度
int visibleGap = scrollViewHeight - absoluteTop;
//float ratio = child浮现的高度/child的高度
float ratio = visibleGap / (float) childHeight;
//确保ratio是在0~1的范围
discrollInterface.onDiscroll(clamp(ratio, 1f, 0f));
}else{
discrollInterface.onResetDiscroll();
}
}
}

/**
* 求三个数的中间大小的一个数
* @param value 输入的值
* @param max 最大值限制
* @param min 最小值限制
*/
public static float clamp(float value, float max, float min){
return Math.max(Math.min(value, max), min);
}
}


注:在 onScrollChanged 中循环遍历对每一个子 View 都进行操作,实际上只需要对最后一个 Item 进行动画属性的设置,这边对所有的 Item 都进行了设置,在性能上实际是由一点影响的,正常情况下,这里的 Item 不会很多,如果说 Item 比较多的话,可以考虑像 ListView 记录最后一个 Item,在滑动的时候根据计算进行重新获取,每次绘制的时候只需要调用这个 Item 执行动画即可。

七、附

代码链接:http://download.csdn.net/download/qq_18983205/10008170
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  动画 框架