先看看几种展示效果图:
效果展示.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>
网友评论