文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android系统控件获取自定义属性

2022-06-06 14:12

关注

我们如果想在ImageView,Button,TextView等系统控件中在XML中配置自定义属性该如何实现呢?例如我们有一个scrollView,在ScrollView里面有上述的一些控件的自定义属性,实现在滑动Scrollview时,里面的控件根据滑动的距离执行各自的动画进度。scrollivew里包含的这些控件可以是任意常用的控件,如 ImageView,Button,TextView等。我们将给这些普通的系统控件配置自定义属性!看到这里是不是觉得无法实现,因为系统的ImageView,Button等是无法识别我们自定义的属性值的,系统的控件怎么识别我们随便定义的属性呢。今天我们就来解决这个问题,解决这个问题的意义在于让系统控件能像我们自定义控件一样,配置了属性就可以执行相应的动画。我们先来看一下运行效果,完整项目见:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn13

首先我们看下自定义的控件的xml布局文件:



AnimatorScrollView(重写了Scrollview) --->AnimatorLinerLayout(重写了LinearLayout)--->包含的系统控件。

现在我们来解释下为什么要重写AnimatorScrollView与AnimatorLinerLayout以及如何让系统控件如Imageview等来识别我们的自定义属性。

1.1  为什么需要自定义AnimatorScrollView

为什么要重写AnimatorScrollView,这个是为了重写


protected void onScrollChanged(int l, int t, int oldl, int oldt)

目的是用来获取滑动的距离t的,然后t/控件的height就可以得出一个比例,执行每个内部控件的动画(透明度,平移X,平移Y等)的比例。

1.2 为什么需要AnimatorLinerLayout

这个是关键,为了解决系统控件(如Imageview)不识别我们的自定义属性的问题。

AnimatorLinerLayout继承于LinearLayout,我们自定义LinearLayout,无非是想改变LinearLayout的行为。那么想改变什么行为呢。我们想利用AnimatorLinerLayout来获取AnimatorLinerLayout包含的各个系统控件,并且解析到为系统控件配置的自定义属性值。这个我们很容易用一个for循环获取到各个子控件及相关XML自定义属性的值。但是我们获取到了这些自定义控件属性又能如何,imageview等系统控件又不识别,就不能执行动画。那我们获取这些自定义属性给谁用??

我们可以在imageview外再包裹一个自定义父布局ViewGroup,然后把这些获取到的自定义属性(动画属性值)赋予这个包裹的VIEWGROUP,然后让父布局可以根据属性值来执行动画,那么里面的imageview是不是也就跟着动起来了呢?这个想法应该可以实现,整体布局都执行动画飞了起来,子布局自然就跟着动了起来,相当于我们的系统控件(如Imageview)执行了动画。这是一个瞒天过海的做法,关于如何在LinearLayout addView之前给每一个系统控件包裹VIEWGROUP的事情,就交给了我们自定义的LinearLayout:AnimatorLinerLayout。这就是我们为什么需要AnimatorLinerLayout的原因:包裹+动画 = 子控件动画 = 瞒天过海

我们总结一下上面的分析:

1. 自定义LinearLayout:DisScrollviewContent,改变布局结构,用自定义VIEWGROUP包裹系统控件如imageview等。

2. 自定义VIEWGROUP(用于包裹)

3. 自定义Scrollview:DisScrollview,根据滑动的距离来计算动画执行的进度比例。

2.1AnimatorLinerLayout(自定义LinearLayout)


public class AnimatorLinerLayout extends LinearLayout {
    public AnimatorLinerLayout(Context context) {
        this(context, null);
    }
    public AnimatorLinerLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public AnimatorLinerLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
    }
    
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        ////从attrs所有参数里提取自定义属性值,并保持在MyLayoutParams对象里,以供“自定义包裹VIEWGROUP"使用并执行动画。
        return new AnimatorLayoutParams(getContext(), attrs);
    }
    //1.考虑到系统控件不识别自定义属性,所以我门要考虑给控件包一层帧
    //2.这里采取有父容器组件给子容器包裹一层的方式
    //3.系统是通过夹杂i 布局文件,然后调用VIEW的addView来进行加载的
    //4.那么此时我门进行偷天换日
    
    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        //获取自定义属性,这时考虑到自定义属性在子控件当中,
        //那么系统控件不识别自定义属性,怎么让自定义属性到这个里面来
        //来看源码
        //根据源码流程-->先调用generateLayoutParams组装XML属性参数
        //在调用addView进行添加,所以,自定义属性在generateLayoutParams中进行组装获取
        //在addView当中将具体的值进行封装
        AnimatorLayoutParams layoutParams = (AnimatorLayoutParams) params;
        AnimatorFramelayout view = new AnimatorFramelayout(child.getContext());
        if (!isDiscrollvable(layoutParams)) {
            //没有自定义属性的系统控件,我们就不需要外层包裹一个“自定义包裹VIEWGROUP"视图。直接addview即可。
            super.addView(view);
        } else {
            //有自定义属性的系统控件,我们需要外层包裹一个“自定义包裹VIEWGROUP"视图。
            view.addView(child);
            view.setmDiscrollveAlpha(layoutParams.mDiscrollveAlpha);
            view.setmDiscrollveFromBgColor(layoutParams.mDiscrollveFromBgColor);
            view.setmDiscrollveToBgColor(layoutParams.mDiscrollveToBgColor);
            view.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleX);
            view.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleY);
            view.setmDisCrollveTranslation(layoutParams.mDisCrollveTranslation);
            super.addView(view, params);
        }
        //至此到这一步就已经获取到了自己的自定义属性,可以进行操作了
    }
    private boolean isDiscrollvable(AnimatorLayoutParams layoutParams) {
        return layoutParams.mDiscrollveAlpha ||
                layoutParams.mDiscrollveScaleX ||
                layoutParams.mDiscrollveScaleY ||
                layoutParams.mDisCrollveTranslation != -1 ||
                (layoutParams.mDiscrollveFromBgColor != -1 &&
                        layoutParams.mDiscrollveToBgColor != -1);
    }
    
    private class AnimatorLayoutParams extends LinearLayout.LayoutParams {
        private boolean mDiscrollveAlpha;
        private boolean mDiscrollveScaleX;
        private boolean mDiscrollveScaleY;
        private int mDisCrollveTranslation;
        private int mDiscrollveFromBgColor;
        private int mDiscrollveToBgColor;
        private AnimatorLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            //没有传属性过来,给默认值FALSE
            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();
        }
    }
}

想要系统获取我们自定义的属性,关键就是重写这两个函数:

generateLayoutParams:这个函数在加载XML布局时自动调用,可以获取到每一个系统控件配置的布局参数,包括自定义参数。每加载一个系统控件(如Imageview),则调用一次这个函数。

addView:这个函数是在generateLayoutParams之后执行,在这里我们可以获取到generateLayoutParams函数返回的MyLayoutParams里的自定义属性值。在addview系统控件(如Imageview)之前,先创建并添加一个“自定义包裹VIEWGROUP"视图,然后将自定义属性赋给这个视图,最后在把系统控件addview到"自定义包裹VIEWGROUP"里,从而实现了在代码中为XML里的每一个系统控件外层包裹一个“自定义包裹VIEWGROUP"视图。

OK,至此我们已经实现了在系统控件外包裹一层可以识别自定义属性的VIEWGROUP父布局,接下来我们就来看一下这个自定义VIEWGROUP是如何执行动画的。

2.1 自定义VIEWGROUP:  AnimatorFramelayout

public class AnimatorFramelayout extends FrameLayout implements DiscrollInterface {
    public AnimatorFramelayout(@NonNull Context context) {
        this(context, null);
    }
    public AnimatorFramelayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public AnimatorFramelayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    //保存自定义属性
    //定义很多的自定义属性
    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;
    }
    @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));
        }
    }
    @Override
    public void onResetDiscroll() {
        if (mDiscrollveAlpha) {
            setAlpha(0);
        }
        if (mDiscrollveScaleX) {
            setScaleX(0);
        }
        if (mDiscrollveScaleY) {
            setScaleY(0);
        }
        //平移动画  int值:left,right,top,bottom    left|bottom
        if (isTranslationFrom(TRANSLATION_FROM_BOTTOM)) {//是否包含bottom
            setTranslationY(mHeight);//height--->0(0代表恢复到原来的位置)
        }
        if (isTranslationFrom(TRANSLATION_FROM_TOP)) {//是否包含bottom
            setTranslationY(-mHeight);//-height--->0(0代表恢复到原来的位置)
        }
        if (isTranslationFrom(TRANSLATION_FROM_LEFT)) {
            setTranslationX(-mWidth);//mWidth--->0(0代表恢复到本来原来的位置)
        }
        if (isTranslationFrom(TRANSLATION_FROM_RIGHT)) {
            setTranslationX(mWidth);//-mWidth--->0(0代表恢复到本来原来的位置)
        }
    }
    private boolean isTranslationFrom(int translationMask) {
        if (mDisCrollveTranslation == -1) {
            return false;
        }
        //fromLeft|fromeBottom & fromBottom = fromBottom
        return (mDisCrollveTranslation & translationMask) == translationMask;
    }
}

我们发现自定义控件里实现了接口DiscrollvableInterface并重写了

void onDiscrollve(float ratio)  //根据比例,执行动画进度

void onResetDiscrollve();//逆向动画,恢复到初始状态。

在这两个函数里会根据自定义属性值与ratio来执行 这个“自定义VIEWGROUP包裹”的动画,从而内部包含的系统控件(如Imageview)等也会跟着动起来。那这两个函数是在什么地方调用的,以及ratio是怎么算出来的,那这个与Scrollview的滑动有关系。那我们就来看一下自定义Scrollview。

2.2自定义Scrollview

public class AnimatorScrollView extends ScrollView {
    private AnimatorLinerLayout mContent;
    public AnimatorScrollView(Context context) {
        super(context);
    }
    public AnimatorScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public AnimatorScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //渲染完毕之后
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContent = (AnimatorLinerLayout) getChildAt(0);
    }
    @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) {
        //监听滑动程度--CHILD从下面冒出来多少距离
        //需要一个百分比来执行动画,
        //百分比为  滑出高度/child实际高度=百分比
        //实际高度
        int scrollViewHeight = getHeight();
        //监听滑动的程度---childView从下面冒出来多少距离/childView.getHeight();----0~1:动画执行的百分比ratio
        //动画执行的百分比ratio控制动画执行
        for (int i = 0; i < mContent.getChildCount(); i++) {
            View child = mContent.getChildAt(i);
            int childHeight = child.getHeight();
            if (!(child instanceof DiscrollInterface)) {
                continue;
            }
            //接口回掉,传递执行的百分比给MyFrameLayout
            //低耦合高内聚
            DiscrollInterface discrollInterface = (DiscrollInterface) child;
            //child离parent顶部的高度
            int childTop = child.getTop();
            //滑出去的这一截高度: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();
            }
        }
    }
    
    private float clamp(float value, float max, float min) {
        return Math.max(Math.min(value, max), min);
    }
}

K,所有流程就分析完了。现在总结一下思路:

1. 自定义LinearLayout的addview,让添加imageview等系统控件前,先在外层包裹一个自定义VIEWGROUP,并赋予它自定义属性的配置。

2. 自定义VIEWGROUP,接收滑动的ratio来执行动画进度

3. 自定义Scrollview,计算滑动的ratio,并调用自定义VIEWGROUP里的执行动画函数。

完整项目

buder得儿得儿以得儿以得儿得儿 原创文章 235获赞 112访问量 27万+ 关注 私信 展开阅读全文
作者:buder得儿得儿以得儿以得儿得儿


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯