Android仿拼多多拼团堆叠头像详解手机开发

序言

做电商的都知道,作为商品的一种售卖方式,拼团是是提供商品售卖的一种及时有效的方式,而在拼团市场中,拼多多无疑是做的最好的一家。于是,研究拼多多的售卖方式之后,我们的产品也开始了这方面的开发。本文将要给大家介绍的就是通过自定义的方式实现堆叠头像,这种效果在直播app中非常常见。下面是部分效果:
这里写图片描述

通过分析,上面是一个使用ViewPager实现的一个可以左右无线循环的Galllery,相关实现可以访问我之前的介绍:PageTransformer使用简介
下面是一个列表的方式,可以通过下拉来加载更多的Cell数据,也比较简单。对于组合头像的实现也是比较简单的,其实就是一个简单的流式布局,在本篇实现上,本文也参考了张鸿洋的FlowLayout,对于流式布局来说,只要按照某种线性规则依次排列即可。

我相信很多朋友之前一定遇到过这种需求:富文本自动换行,如下所示:
这里写图片描述
要实现这种富文本换行,最重要的就是对onMeasure方法,通常的做法是,测量出子View的宽度,当大于屏幕的宽度的时候就换行(当然,需要考虑文字本来就很长,一行显示不下的情况)。相关代码如下:

@Override 
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
 
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 
 
 
        //AT_MOST 
        int width = 0; 
        int height = 0; 
        int rawWidth = 0;//当前行总宽度 
        int rawHeight = 0;// 当前行高 
 
        int count = getChildCount(); 
        for (int i = 0; i < count; i++) { 
            View child = getChildAt(i); 
            if(child.getVisibility() == GONE){ 
                if(i == count - 1){ 
                    //最后一个child 
                    height += rawHeight; 
                    width = Math.max(width, rawWidth); 
                } 
                continue; 
            } 
 
            measureChild(child, widthMeasureSpec, heightMeasureSpec); 
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 
 
            int childWidth = child.getMeasuredWidth()  + lp.leftMargin + lp.rightMargin; 
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 
            Log.e("=====", "childWidth 1: " + childWidth); 
            if(rawWidth + childWidth > widthSpecSize - getPaddingLeft() - getPaddingRight()){ 
                //换行 
                width = Math.max(width, rawWidth); 
                rawWidth = childWidth; 
                height += rawHeight; 
                rawHeight = childHeight; 
            } else { 
                rawWidth += childWidth; 
                rawHeight = Math.max(rawHeight, childHeight); 
            } 
 
            if(i == count - 1){ 
                width = Math.max(rawWidth, width); 
                height += rawHeight; 
            } 
        } 
 
        setMeasuredDimension( 
                widthSpecMode == MeasureSpec.EXACTLY ? widthSpecSize : width + getPaddingLeft() + getPaddingRight(), 
                heightSpecMode == MeasureSpec.EXACTLY ? heightSpecSize : height + getPaddingTop() + getPaddingBottom() 
        ); 
    } 

实现

说了这么多,那么具体怎么实现的呢?由于时间关系,这里我就直接贴代码了。首先自定义ViewGrop,实现后一个头像会覆盖一部分到前一个头像上,为了方便使用者控制堆叠头像的重叠大小,我们通过自定义属性来解决。

PileView.java

public class PileView extends ViewGroup { 
protected float vertivalSpace;//垂直间隙 
protected float pileWidth=0;//重叠宽度 
public PileView(Context context) { 
this(context, null, 0); 
} 
public PileView(Context context, AttributeSet attrs) { 
this(context, attrs, 0); 
} 
public PileView(Context context, AttributeSet attrs, int defStyleAttr) { 
super(context, attrs, defStyleAttr); 
initAttr(context, attrs); 
} 
private void initAttr(Context context, AttributeSet attrs) { 
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PileLayout); 
vertivalSpace = ta.getDimension(R.styleable.PileLayout_PileLayout_vertivalSpace, dp2px(4)); 
pileWidth = ta.getDimension(R.styleable.PileLayout_PileLayout_pileWidth, dp2px(10)); 
ta.recycle(); 
} 
@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 
//AT_MOST 
int width = 0; 
int height = 0; 
int rawWidth = 0;//当前行总宽度 
int rawHeight = 0;// 当前行高 
int rowIndex = 0;//当前行位置 
int count = getChildCount(); 
for (int i = 0; i < count; i++) { 
View child = getChildAt(i); 
if(child.getVisibility() == GONE){ 
if(i == count - 1){ 
//最后一个child 
height += rawHeight; 
width = Math.max(width, rawWidth); 
} 
continue; 
} 
//调用measureChildWithMargins 而不是measureChild 
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 
int childWidth = child.getMeasuredWidth()  + lp.leftMargin + lp.rightMargin; 
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 
if(rawWidth + childWidth  - (rowIndex > 0 ? pileWidth : 0)> widthSpecSize - getPaddingLeft() - getPaddingRight()){ 
//换行 
width = Math.max(width, rawWidth); 
rawWidth = childWidth; 
height += rawHeight + vertivalSpace; 
rawHeight = childHeight; 
rowIndex = 0; 
} else { 
rawWidth += childWidth; 
if(rowIndex > 0){ 
rawWidth -= pileWidth; 
} 
rawHeight = Math.max(rawHeight, childHeight); 
} 
if(i == count - 1){ 
width = Math.max(rawWidth, width); 
height += rawHeight; 
} 
rowIndex++; 
} 
setMeasuredDimension( 
widthSpecMode == MeasureSpec.EXACTLY ? widthSpecSize : width + getPaddingLeft() + getPaddingRight(), 
heightSpecMode == MeasureSpec.EXACTLY ? heightSpecSize : height + getPaddingTop() + getPaddingBottom() 
); 
} 
@Override 
protected void onLayout(boolean changed, int l, int t, int r, int b) { 
int viewWidth = r - l; 
int leftOffset = getPaddingLeft(); 
int topOffset = getPaddingTop(); 
int rowMaxHeight = 0; 
int rowIndex = 0;//当前行位置 
View childView; 
for( int w = 0, count = getChildCount(); w < count; w++ ){ 
childView = getChildAt(w); 
if(childView.getVisibility() == GONE) continue; 
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams(); 
// 如果加上当前子View的宽度后超过了ViewGroup的宽度,就换行 
int occupyWidth = lp.leftMargin + childView.getMeasuredWidth() + lp.rightMargin; 
if(leftOffset + occupyWidth + getPaddingRight() > viewWidth){ 
leftOffset = getPaddingLeft();  // 回到最左边 
topOffset += rowMaxHeight + vertivalSpace;  // 换行 
rowMaxHeight = 0; 
rowIndex = 0; 
} 
int left = leftOffset + lp.leftMargin; 
int top = topOffset + lp.topMargin; 
int right = leftOffset+ lp.leftMargin + childView.getMeasuredWidth(); 
int bottom =  topOffset + lp.topMargin + childView.getMeasuredHeight(); 
childView.layout(left, top, right, bottom); 
// 横向偏移 
leftOffset += occupyWidth; 
// 试图更新本行最高View的高度 
int occupyHeight = lp.topMargin + childView.getMeasuredHeight() + lp.bottomMargin; 
if(rowIndex != count - 1){ 
leftOffset -= pileWidth; 
} 
rowMaxHeight = Math.max(rowMaxHeight, occupyHeight); 
rowIndex++; 
} 
} 
@Override 
public LayoutParams generateLayoutParams(AttributeSet attrs) { 
return new MarginLayoutParams(getContext(), attrs); 
} 
@Override 
protected LayoutParams generateDefaultLayoutParams() { 
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 
} 
@Override 
protected LayoutParams generateLayoutParams(LayoutParams p) { 
return new MarginLayoutParams(p); 
} 
public float dp2px(float dpValue) { 
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics()); 
} 
}

自定义的属性如下:

<declare-styleable name="PileLayout"> 
<attr name="PileLayout_vertivalSpace" format="dimension"/> 
<attr name="PileLayout_pileWidth" format="dimension"/> 
</declare-styleable>

为了方便用户使用,我们在PileView的基础上再封装一下,封装完成后,只需要用户提供数据源即可实现头像堆叠。例如,下面的代码给控件设置数据源即可:

List<String> urls=new ArrayList<>(); 
urls.clear(); 
urls.add("http://ohe65w0xx.bkt.clouddn.com/u=2263418180,3668836868&fm=206&gp=0.jpg"); 
urls.add("http://ohe65w0xx.bkt.clouddn.com/u=3637404049,2821183587&fm=214&gp=0.jpg"); 
urls.add("http://ohe65w0xx.bkt.clouddn.com/avert.png"); 
//设置数据源 
itemAvertView.setAvertImages(urls);

要完成上面的封装,主要会涉及如下的代码:
PileAvertView.java

public class PileAvertView extends LinearLayout { 
@BindView(R.id.pile_view) 
PileView pileView; 
private Context context = null; 
public static final int VISIBLE_COUNT = 3;//默认显示个数 
public PileAvertView(Context context) { 
this(context, null); 
this.context = context; 
} 
public PileAvertView(Context context, AttributeSet attrs) { 
super(context, attrs); 
this.context = context; 
initView(); 
} 
private void initView() { 
View view = LayoutInflater.from(context).inflate(R.layout.layout_group_pile_avert, this); 
ButterKnife.bind(view); 
} 
public void setAvertImages(List<String> imageList) { 
setAvertImages(imageList,VISIBLE_COUNT); 
} 
//如果imageList>visiableCount,显示List最上面的几个 
public void setAvertImages(List<String> imageList, int visibleCount) { 
List<String> visibleList = null; 
if (imageList.size() > visibleCount) { 
visibleList = imageList.subList(imageList.size() - 1 - visibleCount, imageList.size() - 1); 
} 
pileView.removeAllViews(); 
for (int i = 0; i < imageList.size(); i++) { 
CircleImageView image= (CircleImageView) LayoutInflater.from(context).inflate(R.layout.item_group_round_avert, pileView, false); 
CommonImageUtil.loadImage(imageList.get(i), image); 
pileView.addView(image); 
} 
} 
}

相关的布局:
layout_group_pile_avert.xml

<?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" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:gravity="center_horizontal"> 
<com.lanshan.shihuicommunity.grouppurchase.view.PileView 
        android:id="@+id/pile_view" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
app:PileLayout_pileWidth="10dp"/> 
</LinearLayout>

item_group_round_avert.xml

<?xml version="1.0" encoding="utf-8"?> 
<com.makeramen.rounded.CircleImageView xmlns:android="http://schemas.android.com/apk/res/android" 
xmlns:app="http://schemas.android.com/apk/res-auto" 
android:id="@+id/circle_iamge" 
android:layout_width="30dp" 
android:layout_height="30dp" 
android:orientation="vertical" 
app:round_borderColor="#ffffff" 
app:round_borderWidth="1dp" />

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5656.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论