安卓自定义View(四)重叠View(灵感来自头像重叠)

本文介绍了一种自定义View实现的可水平滚动、动态添加头像数量并支持点击变色的功能。通过继承ViewGroup并利用HorizontalScrollView,实现了水平滚动效果及动画展示。

最近同事需要实现如下效果的一个功能,我闲来无事就试着实现了一下,以下是具体功能的实现步骤:

功能要求:
  • 可以水平滚动
  • 动态添加个数不定
  • 可点击 点击后变为选中色

在这里插入图片描述

功能实现:
  • 自定义View
package com.light.mytext.mycustomview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.light.mytext.R;
import com.light.mytext.utiles.DensityUtil;

import java.util.ArrayList;

/**
 * Created by HARRY on 2019/1/18 0018.
 */

public class DiscussionAvatarView extends ViewGroup implements View.OnClickListener {
    /**
     * 图片的宽高
     */
    private int mItemViewWidth;
    private int mItemViewHeight;
    /**
     * 头像间的距离
     */
    private float mSpace;
    private Context mContext;
    private LayoutInflater mInflater;
    /**
     * 是否最后一个显示完全
     */
    private boolean mIsLastComplete;
    /**
     * 最大头像数目
     */
    private int mMaxCount;
    /**
     * 当前移动的偏移量
     */
    private int mCurrentOffset;
    /**
     * 移动的属性动画
     */
    private ValueAnimator animator;
    /**
     * 是否显示动画效果
     */
    private boolean mIsShowAnimation;
    /**
     * 监听
     */
    private boolean mIsShowFrame;
    private int mFrameColor;
    //重写点击事件
    private OnItemViewClickListener mOnItemClickListener = null;//点击事件
    //选中图片R.mipmap.anniu115
    private Integer[] pic = {R.mipmap.anniu5, R.mipmap.anniu511};
    //选中的index
    private int ChooseIndex = 0;
    private ArrayList<String> listData = new ArrayList();

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

    public DiscussionAvatarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DiscussionAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    private void initView(Context context, AttributeSet attrs) {
        this.mContext = context;
        this.mInflater = LayoutInflater.from(mContext);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DiscussionAvatarView);
        if (array != null) {
            mSpace = array.getFloat(R.styleable.DiscussionAvatarView_space, (float) 0.8);
            mMaxCount = array.getInteger(R.styleable.DiscussionAvatarView_maxCount, 6);
            mIsLastComplete = array.getBoolean(R.styleable.DiscussionAvatarView_isLastComplete, true);
            mIsShowAnimation = array.getBoolean(R.styleable.DiscussionAvatarView_isShowAnimation, true);
            mIsShowFrame = array.getBoolean(R.styleable.DiscussionAvatarView_isShowFrame, true);
            mFrameColor = array.getColor(R.styleable.DiscussionAvatarView_frameColor, Color.RED);
            mItemViewWidth = array.getInteger(R.styleable.DiscussionAvatarView_iviewWidth, 150);//item的宽
            mItemViewHeight = array.getInteger(R.styleable.DiscussionAvatarView_iviewHeight, 60);//item的高

            array.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int heiMeasure = MeasureSpec.getSize(heightMeasureSpec);
        int heiMode = MeasureSpec.getMode(heightMeasureSpec);
        int widMode = MeasureSpec.getMode(widthMeasureSpec);
        int widMeasure = MeasureSpec.getSize(widthMeasureSpec);

        int wid = 0;
        int hei = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            LayoutParams lp = child.getLayoutParams();
            lp.width = DensityUtil.dip2px(mContext, mItemViewWidth);
            lp.height = DensityUtil.dip2px(mContext, mItemViewHeight);
            child.setLayoutParams(lp);
            // 测量子View的宽和高,系统提供的measureChild
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 子View占据的宽度
            int childWidth = child.getMeasuredWidth();
            // 子View占据的高度
            int childHeight = child.getMeasuredHeight();

            if (i < mMaxCount) {
                if (i == 0) {
                    wid = wid + childWidth;
                } else {
                    wid = (int) (wid + childWidth * mSpace);
                }
            }
            hei = Math.max(hei, childHeight);
            child.setOnClickListener(this);
        }
        //如果是exactly使用测量宽和高,否则使用自己设置的宽和高
        setMeasuredDimension((widMode == MeasureSpec.EXACTLY) ? widMeasure : wid,
                (heiMode == MeasureSpec.EXACTLY) ? heiMeasure : hei);
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();

        int left = -mCurrentOffset;
        int top = 0;
        int right = -mCurrentOffset;
            for (int i = count - 1; i > -1; i--) {//配合第一个元素置顶效果  数据反着放  间距也反着计算 左边叠加右边
                View child;
                child = getChildAt(i);
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();

                if (i == count - 1) {
                    right = right + childWidth;
                } else {
                    right = (int) (right + childWidth * mSpace);
                }
                child.layout(left, top, right, childHeight);
                left = (int) (left + childWidth * mSpace);
            }
    }

    /**
     * 初始化数据
     *
     * @param list
     */
    public void initDatasList(ArrayList<String> list) {
        if (list == null) {
            return;
        }
        listData = list;
        initDatas();
    }

    /**
     * 初始化数据
     */
    public void initDatas() {
        if (listData == null) {
            return;
        }
        removeAllViews();
        int size = listData.size();
        mMaxCount = size;
        for (int i = size - 1; i > -1; i--) {//反向添加 设置最左边置顶的效果
            View view2 = mInflater.inflate(R.layout.avatar, null);
            ImageView iv = (ImageView) view2.findViewById(R.id.imv_pic);
            TextView tv = (TextView) view2.findViewById(R.id.tv_num);
            tv.setText("第" + (i) + "个");
            if (i % 2 == 0) {
                tv.setTextColor(getResources().getColor(R.color.white));
            } else {
                tv.setTextColor(getResources().getColor(R.color.black));
            }
            if (ChooseIndex == i) {
                Glide.with(mContext).load(R.mipmap.anniu115).into(iv);
                tv.setTextColor(getResources().getColor(R.color.white));
            } else {
                Glide.with(mContext).load(pic[i % 2]).into(iv);
            }
            this.addView(view2);
        }
    }

    @Override
    public void onClick(View view) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            if (getChildAt(i).equals(view)) {
                Log.i("DiscussionAvatarView==", "点击的是第" + (count - i - 1) + "个");
                ChooseIndex = count - i - 1;
                mOnItemClickListener.onViewClick(ChooseIndex);
            }

        }
        initDatas();

    }

    /**
     * 点击事件
     */
    public interface OnItemViewClickListener {
        /**
         * 重试按钮点击
         */
        void onViewClick(int poi);
    }

    /**
     * 设置重试点击事件
     *
     * @param l 重试的点击事件
     */
    public void setOnItemViewClickListener(OnItemViewClickListener l) {
        mOnItemClickListener = l;
    }
}

  • 调用方法

由于要实现水平滚动,所以结合了HorizontalScrollView使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
>

    <HorizontalScrollView
        android:id="@+id/scrollview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scrollbars="none"
        android:layout_gravity="left">

        <com.light.mytext.mycustomview.DiscussionAvatarView
            android:id="@+id/daview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:isLastComplete="true"
            app:isShowAnimation="false"
            app:isShowFrame="false"
            app:iviewHeight="48"
            app:iviewWidth="120"
            app:maxCount="6"
            app:space="0.8"
            ></com.light.mytext.mycustomview.DiscussionAvatarView>
    </HorizontalScrollView>



</LinearLayout>

Java代码实现:

package com.light.mytext.mycustomview

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.light.mytext.R
import com.light.mytext.utiles.ToastUtils
import kotlinx.android.synthetic.main.activity_my_pin_jie.*
import java.util.*

class MyPinJieActivity : AppCompatActivity() {
    private val mDatas = ArrayList<String>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_pin_jie)
        initTestDatas()
        daview.initDatasList(mDatas)


//自动滚动到最后一个item
        /* Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                scrollview.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
            }
        },1000);*/
        daview.setOnItemViewClickListener { poi ->
            ToastUtils.show(
                this@MyPinJieActivity,
                "点击的是第" + poi + "个"
            )
        }
    }
    private fun initTestDatas() {
        for (i in 0..7) {
            mDatas.add("")
        }
    }
}
  • 资源文件
    attrs.xml
<declare-styleable name="DiscussionAvatarView">
        <!--头像的半径,dp为单位-->
        <attr name="iviewWidth" format="reference|integer" />
        <attr name="iviewHeight" format="reference|integer" />
        <!--头像间的距离,为头像直径的长度的百分比,dp为单位-->
        <attr name="space" format="reference|float" />
        <!--最多显示多少个头像-->
        <attr name="maxCount" format="reference|integer" />
        <!--是否最后一个显示完全,默认是true-->
        <attr name="isLastComplete" format="reference|boolean" />
        <!--是否显示动画效果-->
        <attr name="isShowAnimation" format="reference|boolean" />
        <!--是否显示边框-->
        <attr name="isShowFrame" format="reference|boolean" />
        <!--边框颜色-->
        <attr name="frameColor" format="reference|color" />
    </declare-styleable>

avatar.xml

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

    <ImageView
        android:id="@+id/imv_pic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/anniu5"
        android:scaleType="fitXY"></ImageView>

    <TextView
        android:id="@+id/tv_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:textColor="#000000"
        android:layout_centerInParent="true"
        android:textSize="15sp" />
</RelativeLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值