Android实现展开收起动画的悬浮按钮

本文介绍了一种使用属性动画实现控件伸缩效果的方法,通过动态改变控件宽度,实现展开与收缩动画。文章详细解释了实现原理,包括获取子控件准确尺寸、避免文字换行影响及代码实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果图:

实现原理

    利用属性动画的特性,动态地改变控件宽度。

    这里唯一要注意的坑点在于我们一定要拿到准确的子控件(即内部几个view)的宽度与高度 才能精准地对要伸展以及收缩的宽度进行控制。同时,为了不让子控件内部由于文字挤压所带来的换行影响,这里可以设置textview的maxLine属性为1。

    由于现在需求急于上线,这里先直接上代码了~等忙完这阵子再来细细分享实现过程中遇到的坑以及一些代码逻辑的实现原理。

Coding Time

class ExpandAnimationView : LinearLayout {

    private lateinit var mImageView: ImageView

    private val mAnimatorSet = AnimatorSet()

    private var mExpandWidth = 0

    private val mExpandContainer = LinearLayout(context)

    private val mExpandItems = listOf(ExpandItem(R.drawable.ic_next, "收起"), ExpandItem(R.drawable.ic_share, "分享"))

    private var mIsExpanding = false

    private var mClickListener: OnExpandViewClickListener? = null

    constructor(context: Context): this(context, null)

    constructor(context: Context, attrs: AttributeSet?): super (context, attrs)

    init {
        // 设置父级控件的属性
        orientation = HORIZONTAL
        gravity = Gravity.CENTER_VERTICAL
        background = ResourcesCompat.getDrawable(context.resources, R.drawable.bg_expand_view, null)
        mImageView = ImageView(context)
        addView(mImageView)
        mImageView.apply {
            layoutParams = LinearLayout.LayoutParams(dip2px(25F), dip2px(25F))
            val p = layoutParams as LayoutParams
            p.setMargins(dip2px(13F), dip2px(13F), dip2px(13F), dip2px(13F))
            layoutParams = p
            setImageResource(R.drawable.ic_add)
        }

        setOnClickListener {
            mImageView.performClick()
        }

        mImageView.setOnClickListener {
            startAnimation()
        }

        // 设置扩展的子控件的属性
        initExpandView()
    }

    private fun initExpandView() {
        if (mExpandItems.isEmpty()) return

        addView(mExpandContainer, 0)

        mExpandContainer.layoutParams = LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT)
        val containerParam = mExpandContainer.layoutParams as LayoutParams
        mExpandContainer.gravity = Gravity.CENTER_VERTICAL

        for ((index, item) in mExpandItems.withIndex()) {
            val textView = TextView(context)
            textView.apply {
                gravity = Gravity.CENTER_VERTICAL
                textView.textSize = 13F
                textView.setTextColor(Color.WHITE)
                textView.text = item.text
                textView.maxLines = 1
                val drawable = ResourcesCompat.getDrawable(context.resources, item.icon, null)
                drawable?.setBounds(0, 0, dip2px(15F), dip2px(15F))
                setCompoundDrawables(drawable, null, null, null)
                compoundDrawablePadding = dip2px(3F)
            }
            mExpandContainer.addView(textView)
            textView.setOnClickListener {
                mClickListener?.onItemClick(item.text)
            }
            val p = textView.layoutParams as LinearLayout.LayoutParams
            p.setMargins(if (index == 0) dip2px(23F) else dip2px(13F), 0, dip2px(13F), 0)
            textView.layoutParams = p
            val divider = View(context)
            divider.setBackgroundColor(Color.parseColor("#fafafa"))
            divider.layoutParams = LinearLayout.LayoutParams(dip2px(1F), dip2px(20F))
            mExpandContainer.addView(divider)
            mExpandWidth += (textView.textSize * item.text.length).toInt() + dip2px(15F + 20F + 1F + 3F)
        }
        mExpandWidth += dip2px(10F + 13F)

        mExpandContainer.alpha = 0F
    }

    // 属性动画原理 与下面实现动画时 属性"expandWidth"相对应
    fun setExpandWidth(width: Float) {
        val p = mExpandContainer.layoutParams
        p.width = width.toInt()
        mExpandContainer.layoutParams = p
    }
    
    // 开始动画
    fun startAnimation() {
        if (mIsExpanding) {
            collapse()
        } else {
            expand()
        }
    }

    private fun expand() {
        if (mIsExpanding) return
        mIsExpanding = true
        val rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", 0F, -45F)
        val alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 1F, 0.5F)
        val expandAnim = ObjectAnimator.ofFloat(this, "expandWidth", 0F, mExpandWidth.toFloat())
        val alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 0F, 1F)
        mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, expandAnim, alphaAnim)
        mAnimatorSet.start()
        mClickListener?.onExpand()
    }

    private fun collapse() {
        if (!mIsExpanding) return
        mIsExpanding = false
        val rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", -45F, 0F)
        val alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 0.5F, 1F)
        val collapseAnim = ObjectAnimator.ofFloat(this, "expandWidth", mExpandWidth.toFloat(), 0F)
        val alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 1F, 0F)
        mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, collapseAnim, alphaAnim)
        mAnimatorSet.start()
        mClickListener?.onCollapse()
    }

    fun setOnExpandViewClickListener(lis: OnExpandViewClickListener) {
        this.mClickListener = lis
    }

    fun destroy() {
        mAnimatorSet.removeAllListeners()
        mAnimatorSet.cancel()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(measuredWidth, dip2px(25F + 26F))
    }

    private fun dip2px(floatVal: Float): Int {
        val density = context.resources.displayMetrics.density
        return (floatVal * density + 0.5F).toInt()
    }

    data class ExpandItem(@DrawableRes val icon: Int, val text: String)

    interface OnExpandViewClickListener {
        fun onExpand()

        fun onCollapse()

        fun onItemClick(tag: String)
    }

}

    若需要Java代码的同学可以自行在AS里通过Tools -> Kotlin -> Show Kotlin Bytecode -> 在右侧弹出的窗口中选择Decompile即可。

    欢迎指出不足~

   Java代码:

public class ExpandAnimationView extends LinearLayout {

    private ImageView mImageView;

    private final AnimatorSet mAnimatorSet = new AnimatorSet();

    private int mExpandWidth = 0;

    private final LinearLayout mExpandContainer = new LinearLayout(getContext());

    private final List<ExpandItem> mExpandItems = new ArrayList<>();

    private boolean mIsExpanding = false;

    private OnExpandViewClickListener mClickListener;

    public ExpandAnimationView(Context context) {
        this(context, null);
    }

    public ExpandAnimationView(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        mExpandItems.add(new ExpandItem(R.drawable.ic_next, "收起"), new ExpandItem(R.drawable.ic_share, "分享"));
        initViews();
    }

    private void initViews() {
        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL);
        setBackground(ResourcesCompat.getDrawable(getContext().getResources(), R.drawable.bg_expand_view, null));
        mImageView = new ImageView(getContext());
        addView(mImageView);
        mImageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(25F), dip2px(25F)));
        LinearLayout.LayoutParams p = (LayoutParams) mImageView.getLayoutParams();
        p.setMargins(dip2px(13F), dip2px(13F), dip2px(13F), dip2px(13F));
        mImageView.setLayoutParams(p);
        mImageView.setImageResource(R.drawable.ic_add);

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mImageView.performClick();
            }
        });

        mImageView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                startAnimation();
            }
        });

        initExpandView();
    }

    private void initExpandView() {
        if (mExpandItems == null || mExpandItems.isEmpty()) return;

        addView(mExpandContainer, 0);
        mExpandContainer.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT));
        ViewGroup.LayoutParams lp = mExpandContainer.getLayoutParams();
        mExpandContainer.setGravity(Gravity.CENTER_VERTICAL);

        for (int i = 0; i < mExpandItems.size(); i++) {
            final TextView textView = new TextView(getContext());
            textView.setGravity(Gravity.CENTER_VERTICAL);
            textView.setTextSize(13F);
            textView.setTextColor(Color.WHITE);
            textView.setText(mExpandItems.get(i).text);
            textView.setMaxLines(1);
            Drawable drawable = ResourcesCompat.getDrawable(getContext().getResources(), mExpandItems.get(i).resourceId, null);
            if (drawable != null) {
                drawable.setBounds(0, 0, dip2px(15F), dip2px(15F));
            }
            textView.setCompoundDrawables(drawable, null, null, null);
            textView.setCompoundDrawablePadding(dip2px(3F));

            mExpandContainer.addView(textView);
            textView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (mClickListener != null) {
                        mClickListener.onItemClick(mExpandItems.get(i).text);
                    }
                }
            });

            LinearLayout.LayoutParams p = (LayoutParams) textView.getLayoutParams();
            p.setMargins(i == 0 ? dip2px(23F) : dip2px(13F), 0, dip2px(13F), 0);
            textView.setLayoutParams(p);
            final View divider = new View(getContext());
            divider.setBackgroundColor(Color.parseColor("#fafafa"));
            divider.setLayoutParams(new LinearLayout.LayoutParams(dip2px(1F), dip2px(20F)));
            mExpandContainer.addView(divider);
            mExpandWidth += (textView.getTextSize() * mExpandItems.get(i).text.length()) + dip2px(15F + 20F + 1F + 3F);
        }

        mExpandWidth += dip2px(10F + 13F);

        mExpandContainer.setAlpha(0);
    }

    // 属性动画原理 与下面实现动画时 属性"expandWidth"相对应
    public void setExpandWidth(float width) {
        LayoutParams p = (LayoutParams) mExpandContainer.getLayoutParams();
        p.width = (int) width;
        mExpandContainer.setLayoutParams(p);
    }

    // 开始动画
    public void startAnimation() {
        if (mIsExpanding) {
            collapse();
        } else {
            expand();
        }
    }

    private void collapse() {
        if (!mIsExpanding) return;
        mIsExpanding = false;
        final ObjectAnimator rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", -45F, 0F);
        final ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 0.5F, 1F);
        final ObjectAnimator collapseAnim = ObjectAnimator.ofFloat(this, "expandWidth", mExpandWidth, 0F);
        final ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 1F, 0F);
        mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, collapseAnim, alphaAnim);
        mAnimatorSet.start();
        if (mClickListener != null) {
            mClickListener.onCollapse();
        }
    }

    private void expand() {
        if (mIsExpanding) return;
        mIsExpanding = true;
        final ObjectAnimator rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", 0F, -45F);
        final ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 1F, 0.5F);
        final ObjectAnimator expandAnim = ObjectAnimator.ofFloat(this, "expandWidth", 0F, mExpandWidth);
        final ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 0F, 1F);
        mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, expandAnim, alphaAnim);
        mAnimatorSet.start();
        if (mClickListener != null) {
            mClickListener.onExpand();
        }
    }

    private int dip2px(float floatVal) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (floatVal * density + 0.5F);
    }

    public void setOnExpandViewClickListener(OnExpandViewClickListener clickListener) {
        this.mClickListener = clickListener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(), dip2px(25F + 26F));
    }

    public void destroy() {
        mAnimatorSet.removeAllListeners();
        mAnimatorSet.cancel();
    }

    class ExpandItem {
        public int resourceId;

        public String text;
    }

    interface OnExpandViewClickListener {
        void onExpand();

        void onCollapse();

        void onItemClick(String tag);
    }
}

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值