可折叠按钮

移动开发 waitig 723℃ 百度已收录 0评论

# 自定义可折叠按钮(改进版)

长期潜水看公众号,在上月底,看到写自定义可折叠按钮的文章,一看觉得效果还可以,就仔细看了一下,看着看着,却发现…这样做是不是有点画蛇添足,浪费时间不说,可扩展性也不好啊.

基于学习(怼作者)的目的,花了点时间去验证了一下,分享给大家,欢迎大家搞基(来怼我啊).

参照效果原文地址: [ 自定义可折叠按钮]

效果图祭天:

效果图

需求分解

仔细看效果图,我们可以知道整个view的效果,大概可以分为2部分 :

  • 左侧图片旋转和缩放
  • 非图片区域 展开/收起

效果图

就这么简单?


需求实现

先把布局画出来
新建布局文件 “view_button_expand.xml”


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mRootView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerVertical="true"
        android:scaleX="0.8"
        android:scaleY="0.8"
        android:scaleType="fitXY"
        android:src="@drawable/test" />

    <LinearLayout
        android:id="@+id/mLinearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginRight="10dp"
        android:layout_toRightOf="@+id/imageView"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:paddingLeft="10dp"
                android:textColor="#FFFFFFFF"
                android:paddingRight="10dp"
                android:text="电影 / 小说 / 美食"
                android:textSize="18sp" />
    <!-- 自己写啦 爱写什么写什么 -->
    </LinearLayout>
</RelativeLayout>

然后新建ExpandButtonLayout 类,去初始化我们的layout,获取我们实现效果需要的view,做好准备工作.
ps : 因为
缩放需要获得 <需要缩放布局> 的宽度
需要在完成缩放之后背景是一个完整的圆,因此动态生成drawable
内外间距需要同时缩放
所以我们添加一个OnGlobalLayoutListener监听器,在回调中来获取整个布局的高度,和需要缩放的宽度,还有缩放部分的内外间距,然后保存起来

关于margin的获取,不清楚的同学,可以去查看各种 LayoutParams 的依赖继承关系,这里就不啰嗦了.

public class ExpandButtonLayout extends RelativeLayout implements Animation.AnimationListener {

    private RelativeLayout mRootView;
    private ImageView imageView;
    private LinearLayout mLinearLayout;

    public ExpandButtonLayout(Context context) {
        super(context);
        init(context, null);
    }

    /*省略其他构造方法*/
    private int savePaddingLeft = 0;
    private int savePaddingRight = 0;
    private int saveMarginLeft = 0;
    private int saveMarginRight = 0;
    private int mLinearLayoutWidth = 0;
    private int allHeight = 0;

    private void init(Context context, AttributeSet attrs) {
        //初始化视图,获取控件引用
        LayoutInflater.from(context).inflate(R.layout.view_button_expand, this, true);
        mRootView = (RelativeLayout) findViewById(R.id.mRootView);
        imageView = (ImageView) findViewById(R.id.imageView);
        mLinearLayout = (LinearLayout) mRootView.findViewById(R.id.mLinearLayout);
        //添加监听,在回调中获取我们需要的值
        ViewTreeObserver vto = mLinearLayout.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                allHeight = getHeight();
                mLinearLayoutWidth = mLinearLayout.getWidth();
                savePaddingLeft = mLinearLayout.getPaddingLeft();
                savePaddingRight = mLinearLayout.getPaddingRight();
                ViewGroup.LayoutParams vglp = mLinearLayout.getLayoutParams();
                if (vglp instanceof ViewGroup.MarginLayoutParams) {
                    ViewGroup.MarginLayoutParams marginVglp = (ViewGroup.MarginLayoutParams) vglp;
                    saveMarginLeft = marginVglp.leftMargin;
                    saveMarginRight = marginVglp.rightMargin;
                }
                mLinearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                //获取高度之后,根据高度渲染背景
                initBackGround();
            }
        });
    }

    /**
     * 设置圆角背景
     */
    private void initBackGround(){
        int fillColor = getResources().getColor(R.color.colorPrimary);
        GradientDrawable gd = new GradientDrawable();//创建drawable
        gd.setColor(fillColor);
        //圆角半径等于高度的一半,合成之后是一个完整的圆
        gd.setCornerRadius(allHeight/2f);
        gd.getPadding(new Rect(0,0,0,0));
        setBackground(gd);
    }
 }

添加我们需要的接口,和记录控件状态的变量,然后我们需要实现AnimationListener ,并在回调中记录对应的状态

    private boolean isExpand = true;//展开状态
    private boolean isAnimation = false;//动画状态

    public void open() {
        //待实现
    }

    public void close() {
        //待实现
    }
    //切换效果
    public void toggle() {
        if (!isAnimation) {
            if (isExpand) {
                close();
            } else {
                open();
            }
        }
    }

    @Override
    public void onAnimationStart(Animation animation) {
        isAnimation = true;
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        isAnimation = false;
        isExpand = !isExpand;
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }

新建一个activity,把我们自己实现的ExpandButtonLayout 写进activity的布局文件中,添加一个按钮,点击事件回调中 调用 ExpandButtonLayout.toggle(),至此,我们的准备工作做好了.


实现效果

前面我们分析了,左边图片是旋转+缩放,先实现这一部分

    public void open() {

        AnimationSet animationSet = new AnimationSet(true);
        animationSet.setDuration(duration);
        animationSet.setAnimationListener(this);
        animationSet.setFillAfter(true);

        RotateAnimation rotateAnimation = new RotateAnimation(270, 360,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animationSet.addAnimation(rotateAnimation);
        Animation scaleAnimation = new ScaleAnimation(1.25f, 1f, 1.25f, 1f,
                ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
        animationSet.addAnimation(scaleAnimation);
        imageView.startAnimation(animationSet);

    }

    public void close() {
        AnimationSet animationSet = new AnimationSet(true);
        animationSet.setDuration(duration);
        animationSet.setAnimationListener(this);
        animationSet.setFillAfter(true);

        RotateAnimation rotateAnimation = new RotateAnimation(360, 270,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animationSet.addAnimation(rotateAnimation);
        Animation scaleAnimation = new ScaleAnimation(1f, 1.25f, 1f, 1.25f,
                ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
        animationSet.addAnimation(scaleAnimation);
        imageView.startAnimation(animationSet);
    }

RotateAnimation和ScaleAnimation的构造也不再啰嗦,不熟悉的同学可以查看对应的源码或文档;

我们需要让图片放大之后还能完整的渲染,因此我们在前面的布局文件中声明了默认scale=0.8,当收起布局的时候我们希望scale=1,所以我们需要放大1.25倍.(这里可能需要多想想),基于本身锚点正中心(0.5,0.5)这个不需要解释了吧,边看边敲代码的同学可以 run一下 看看效果先了.

接下来是右边展开收起这一部分,最开始我是测试了一下使用scale动画,那效果有点惨不忍睹,文字挤压得跟千层饼一样. 后来一想,干嘛不用属性动画?

我们仔细想想我们要收起右边的布局,我们需要在X轴上,把所有的宽度都变成0
layout完成之后的内容宽度
左右外边距
左右内边距

    public void open() {
        //...
        AnimatorSet animatorSet = new AnimatorSet();
        ObjectAnimator animator1 = ObjectAnimator.ofInt(mLinearLayout, "width", 0, mLinearLayoutWidth);
        ObjectAnimator animator2 = ObjectAnimator.ofInt(mLinearLayout, "paddingLeft", 0, savePaddingLeft);
        ObjectAnimator animator3 = ObjectAnimator.ofInt(mLinearLayout, "paddingRight", 0, savePaddingRight);
        ObjectAnimator animator4 = ObjectAnimator.ofInt(mLinearLayout, "marginLeft", 0, saveMarginLeft);
        ObjectAnimator animator5 = ObjectAnimator.ofInt(mLinearLayout, "marginRight", 0, saveMarginRight);
        animatorSet.playTogether(animator1, animator2, animator3, animator4, animator5);
        animatorSet.setDuration(duration).start();
    }
    public void close() {
        //...
        AnimatorSet animatorSet = new AnimatorSet();
        ObjectAnimator animator1 = ObjectAnimator.ofInt(mLinearLayout, "width", mLinearLayoutWidth, 0);
        ObjectAnimator animator2 = ObjectAnimator.ofInt(mLinearLayout, "paddingLeft", savePaddingLeft, 0);
        ObjectAnimator animator3 = ObjectAnimator.ofInt(mLinearLayout, "paddingRight", savePaddingRight, 0);
        ObjectAnimator animator4 = ObjectAnimator.ofInt(mLinearLayout, "marginLeft", saveMarginLeft, 0);
        ObjectAnimator animator5 = ObjectAnimator.ofInt(mLinearLayout, "marginRight", saveMarginRight, 0);
        animatorSet.playTogether(animator1, animator2, animator3, animator4, animator5);
        animatorSet.setDuration(duration).start();

    }

然后我们发现编辑器报错了! 居然报错了!
类似报错信息: could not find property setter method setWidth on LinearLayout
哦,没有这个setter方法啊,那我们造一个

新建一个MyLinearLayout继承LinearLayout,然后扩展5个我们需要用到的方法

public void setWidth(int width) {
        ViewGroup.LayoutParams lp = getLayoutParams();
        if (lp != null) {
            lp.width = width;
            setLayoutParams(lp);
        }
    }

    public void setPaddingLeft(int left) {
        setPadding(left, getPaddingTop(), getPaddingRight(), getPaddingBottom());
    }

    public void setPaddingRight(int right) {
        setPadding(getPaddingLeft(), getPaddingTop(), right, getPaddingBottom());
    }

    public void setMarginLeft(int left) {
        checkMarginLayoutParams();
        if (marginVglp != null) {
            marginVglp.leftMargin = left;
        }
    }

    public void setMarginRight(int right) {
        checkMarginLayoutParams();
        if (marginVglp != null) {
            marginVglp.rightMargin = right;
        }
    }

    private void checkMarginLayoutParams() {
        if (marginVglp == null) {
            ViewGroup.LayoutParams vglp = getLayoutParams();
            if (vglp instanceof ViewGroup.MarginLayoutParams) {
                marginVglp = (ViewGroup.MarginLayoutParams) vglp;
            }
        }
    }

然后把 view_button_expand.xml 和 ExpandButtonLayout.Java 中的 LinearLayout 换成 MyLinearLayout. 跑起来看看效果吧

(事件监听什么的,控件联动效果什么的,小意思啦,issue 提的多了就更新到github去吧,没人提就懒得弄啦,毕竟大家都这么聪明,是吧   /邪恶笑)

gayhub: [ 项目源码]

手动复制: https://github.com/ViewStub/ExpandButton

[ https://github.com/ViewStub ] 专注于安卓前端各种UI效果的开源组织,欢迎大家关注
志同道合的同志 可邮件(github昵称,贡献自己的UI项目名称,自我介绍)到 github@viewstub.com 申请加入

大吉大利,晚上吃鸡!


本文由【waitig】发表在等英博客
本文固定链接:可折叠按钮
欢迎关注本站官方公众号,每日都有干货分享!
等英博客官方公众号
点赞 (0)分享 (0)