美文网首页Androidandroid自定义控件Android自定义控件
自定义的签到打卡头像重叠排列ViewGroup

自定义的签到打卡头像重叠排列ViewGroup

作者: 砂砾han | 来源:发表于2017-06-13 16:51 被阅读264次

    先看看几种展示效果图:

    效果展示.png

    之前在实现一个打卡功能的时候,设计图上要求展示打卡用户的头像,只是这些头像不是正常的从左至右排排坐,如示例图上的3、4排的效果,而是1所展示的效果。如果只是像3或者4这样展示,我们可以用RecyclerView轻松实现。


    思路

    分析下1这种效果的特殊之处,从左至右都是左边的图片压在右边图片的上方,看起来它是从右往左layout的,最容易想到的实现方法有两种:

    • 自定义ViewGroup,重写layout方法,本文就是采用这种方式;
    • 利用RecyclerView的自定义LayoutManager来实现,大致思路是继承RecyclerView.LayoutManager,重写onLayoutChildren来实现,有兴趣的同学可以自己动手试试。
      下面结合代码看看自定义ViewGroup的实现方式。

    具体实现

    我们的主要工作是measure、layout、setData,首先考虑下我们需要自定义哪些属性便于拓展

    <declare-styleable name="CoverView">
            <attr name="coverWidth" format="dimension" />
            <attr name="itemDia" format="dimension" />
            <attr name="display_style">
                <enum name="left_to_right" value="0" />
                <enum name="right_to_left" value="1" />
            </attr>
        </declare-styleable>
    

    这里我定义了三个,两个图片重叠的宽度(coverWidth)、每张图片的直径(itemDia)、展示风格(display_style)、这里定义了两种:left_to_right从左至右顺序摆放,即2、3、4所对应的样式;right_to_left刚好相反,示例中1的效果。

    • measure过程,因为设计只让在限定区域内显示几个完整的头像,所以我们需要根据测量的宽度计算出能显示的最大图片数maxShowCounts,从而知道最终需要展示的是哪些数据。
    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (data == null || data.isEmpty()) return;
            int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight() - getPaddingLeft();
            //根据图片的直径,重叠宽度和父控件允许的最大宽度计算出能显示的最大图片数
            int maxShowCounts = (width - coverWidth) / (itemDia - coverWidth);
            showData.clear();
            if (maxShowCounts < data.size()) {
                for (int i = 0; i < maxShowCounts; i++) {
                    showData.add(data.get(i));
                }
            } else {
                showData.addAll(data);
            }
            showCount = showData.size();
            int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
            setMeasuredDimension(totalWidth, itemDia + getPaddingTop() + getPaddingBottom());
        }
    
    • layout过程,最核心的过程,但其实代码没有几行……
    @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (showData == null || adapter == null) return;
            if (displayStyle == STYLE_RIGHT_TO_LEFT) {
              //如果是right_to_left的效果我们需要将展示的list翻转一下
                Collections.reverse(showData);
            }
            for (int i = 0; i < showCount; i++) {
                ImageView childView = (ImageView) getChildAt(i);
                adapter.onDisplayImage(getContext(), childView, showData.get(i));
                int left;
                // 画个草图,很容易计算
                if (displayStyle == STYLE_RIGHT_TO_LEFT) {
                    left = getPaddingLeft() + (itemDia - coverWidth) * (showCount - i - 1);
                } else {
                    left = getPaddingLeft() + (itemDia - coverWidth) * i;
                }
                int right = left + itemDia;
                int top = getPaddingTop();
                int bottom = getPaddingTop() + itemDia;
                //辛辛苦苦只为这一下
                childView.layout(left, top, right, bottom);
            }
        }
    
    • 为CoverView填充数据,setData其实就是addview()然后requestLayout();
    public void setData(List<T> list) {
            if (list == null || list.isEmpty()) {
                setVisibility(View.GONE);
                return;
            } else {
                setVisibility(View.VISIBLE);
            }
            data = list;
            for (int i = 0; i < data.size(); i++) {
                ImageView iv = getImageView(i);
                if (iv == null) {
                    return;
                }
                addView(iv, generateDefaultLayoutParams());
            }
            requestLayout();
        }
    
    • 这里也需要一个adapter来作为数据与View的桥梁,通过adapter来展示图片和获取ImageView对象,当然还可以做很多其他的事情,例如设置监听…
    public abstract class CoverAdapter<T> {
    
        public abstract void onDisplayImage(Context context, ImageView imageView, T t);
    
        public ImageView generateImageView(Context context) {
            //可以在这里更改Imageview的类型
            CircleImageView circleImageView = new CircleImageView(context);
    //        ImageView imageView = new ImageView(context);
            circleImageView.setBorderColor(Color.parseColor("#dcdcdc"));
            circleImageView.setBorderWidth(4);
            return circleImageView;
        }
    }
    

    如何使用

    贴下我们展示的示例图片代码:

    public class MainActivity extends Activity {
    
        private CoverView coverView;
        private CoverView coverView2;
        private CoverView coverView3;
        private CoverView coverView4;
    
        private CoverAdapter<String> adapter;
    
        private List<String> list;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            coverView = (CoverView) findViewById(R.id.cover_view);
            coverView2 = (CoverView) findViewById(R.id.cover_view2);
            coverView3 = (CoverView) findViewById(R.id.cover_view3);
            coverView4 = (CoverView) findViewById(R.id.cover_view4);
            initData();
    
            adapter = new CoverAdapter<String>() {
                @Override
                public void onDisplayImage(Context context, ImageView imageView, String s) {
                    Picasso.with(MainActivity.this).load(s).into(imageView);
                }
            };
            coverView.setAdapter(adapter);
            coverView.setData(list);
    
            coverView2.setAdapter(adapter);
            coverView2.setData(list);
    
            coverView3.setAdapter(adapter);
            coverView3.setData(list);
    
            coverView4.setAdapter(adapter);
            coverView4.setData(list);
        }
    
        private void initData() {
            list = new ArrayList<>();
            list.add("https://pic4.zhimg.com/02685b7a5f2d8cbf74e1fd1ae61d563b_xll.jpg");
            list.add("https://pic4.zhimg.com/fc04224598878080115ba387846eabc3_xll.jpg");
            list.add("https://pic3.zhimg.com/d1750bd47b514ad62af9497bbe5bb17e_xll.jpg");
            list.add("https://pic4.zhimg.com/da52c865cb6a472c3624a78490d9a3b7_xll.jpg");
            list.add("https://pic3.zhimg.com/0c149770fc2e16f4a89e6fc479272946_xll.jpg");
            list.add("https://pic1.zhimg.com/76903410e4831571e19a10f39717988c_xll.png");
            list.add("https://pic3.zhimg.com/33c6cf59163b3f17ca0c091a5c0d9272_xll.jpg");
            list.add("https://pic4.zhimg.com/52e093cbf96fd0d027136baf9b5cdcb3_xll.png");
            list.add("https://pic3.zhimg.com/f6dc1c1cecd7ba8f4c61c7c31847773e_xll.jpg");
        }
    }
    

    xml文件

     <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#DFEFF0">
    
        <com.hdz.signview.CoverView
            android:id="@+id/cover_view"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentTop="true"
            android:layout_marginTop="40dp"
            android:background="#FEBFB3"
            android:paddingBottom="10dp"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:paddingTop="10dp"
            app:coverWidth="10dp"
            app:display_style="right_to_left"
            app:itemDia="50dp" />
    
        <com.hdz.signview.CoverView
            android:id="@+id/cover_view2"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_below="@id/cover_view"
            android:layout_marginTop="20dp"
            android:background="#009378"
            android:paddingBottom="10dp"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:paddingTop="10dp"
            app:coverWidth="10dp"
            app:display_style="left_to_right"
            app:itemDia="50dp" />
    
        <com.hdz.signview.CoverView
            android:id="@+id/cover_view3"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_below="@id/cover_view2"
            android:layout_marginTop="20dp"
            android:background="#009378"
            android:paddingBottom="10dp"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:paddingTop="10dp"
            app:coverWidth="0dp"
            app:display_style="left_to_right"
            app:itemDia="50dp" />
    
        <!-- coverWidth设置成负值的时候就分开啦-->
        <com.hdz.signview.CoverView
            android:id="@+id/cover_view4"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_below="@id/cover_view3"
            android:layout_marginTop="20dp"
            android:background="#009378"
            android:paddingBottom="10dp"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:paddingTop="10dp"
            app:coverWidth="-10dp"
            app:display_style="left_to_right"
            app:itemDia="50dp" />
    </RelativeLayout>
    

    …………写个简书比造轮子的时间还要长…………
    <p>
    完整的demo已上传到我的GitHub ,有需要的可以去下载,有错误请在评论指出,共同学习进步。
    <p>

    相关文章

      网友评论

      本文标题:自定义的签到打卡头像重叠排列ViewGroup

      本文链接:https://www.haomeiwen.com/subject/jcbdqxtx.html