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" />
时间: 2024-12-13 15:43:02

Android仿拼多多拼团堆叠头像的相关文章

Android 仿京东、拼多多商品分类页的示例代码

最近接了一个项目,要仿照京东写一个商品分类页,但需要滑动右边子分类,左边的主分类也跟着变换,写了个demo,需要的同学可以自取. 先放一个写完之后的样子: 写这个需求的思路也很清晰,首先左边肯定是一个listView,右边也是一个listView,这两个listView要达到一个联动的效果.右边的listView再嵌套一个GridView即可.如下图所示. 所以,我们需要的数据结构也就确定了,应该是数组套数组,也就说护肤大分类下又有子分类商品,类似于这个样子: ok,数据和UI结构确定了,就可以

拼多多APP盈利模式解析分享

给各位拼多多软件的使用者们来详细的解析分享一下盈利模式. 解析分享:    拼多多是团购和微分销的衍生品.   团购购买商品的玩法是:一件商品,如果单卖,要100元,但是如果达到20人购买的话,就可以便宜一点,比如60元也行.   微分销的玩法是:如果你成为我的分销商,你分销的用户购买商品,你就有返利,依此类推,一般到三级.   拼多多既有团购的概念,也有分销的概念,而这种1分购物,1元创富百万的例子更是在微商营销中层出不穷.   举个例子:   [限量250份]4.9元2斤智利进口车厘子(果径

Android仿微信群聊头像_Android

工作中需要实现仿钉钉群头像的一个功能,就是个人的头像拼到一起显示,看了一下市场上的APP好像微信的群聊头像是组合的,QQ的头像不是,别的好像也没有了. 给大家分享一下怎么实现的吧.首先我们先看一下效果图: 好了,下面说一下具体怎么实现的: 实现思路 1.首先获取Bitmap图片(本地.网络) 2.创建一个指定大小的缩略图 3.组合Bitmap图片 很简单,本地图片需要我们从本地读取,如果是网络图片我们也可以根据URL来获取bitmap进行组合 具体实现过程 1.布局文件: <LinearLayo

android仿qq头像点击放大?具体是怎么实现的?望大神指教

问题描述 android仿qq头像点击放大?具体是怎么实现的?望大神指教 android仿qq头像点击放大?具体是怎么实现的?望大神指教 解决方案 可以重新打开一个activity,里面布局放一个大一点的imageview,设置scaletype为充满,将图像传递过去 解决方案二: 他那个点击头像,其实就是跳转至另一个activity,把图片资源也传递了过去,在那个activity放置了一个imageview,然后通过onTouchEvent()触控事件来处理图片的缩放和移动.这个很简单的.

Android仿微信QQ设置图形头像裁剪功能_Android

最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue)! 图片裁剪实现方式有两种,一种是利用系统自带的裁剪工具,一种是使用开源工具Cropper.本节就为大家带来如何使用系统自带的裁剪工具进行图片裁剪~ 还是先来个简单的运行图. 额,简单说下,我待会会把代码写成小demo分享给大家,在文章末尾会附上github链接,需要的可以自行下载~ 下面来简单分

拼多多取消订单方法解析

给各位拼多多软件的使用者们来相机的解析分享一下取消订单的方法. 方法分享:   进入到拼多多商城app中,点击个人中心进入,点击我的订单:   点击全部订单,进入到自己想要退单的订单,直接进入到详情界面后,点击取消订单. 好了,以上的信息就是小编给各位拼多多的这一款软件的使用者们带来的详细的取消订单的方法解析分享的全部内容了,各位看到这里的软件使用者们,小编相信你们现在那是非常得清楚了取消的方法了吧,那么各位朋友们就快去按照小编上面带来的方法去取消自己不想要的订单吧.

拼多多APP申请退款步骤解析分享

给各位拼多多软件的使用者们来详细的解析分享一下申请退款的步骤. 解析分享:   仅退款   1.打开拼多多app,在"个人中心"按钮中点击"我的订单",在订单列表里选择需要申请售后的商品,点击"申请售后":   2.如果仅需退款无需退货,请选择"仅退款".填写退款金额及退款原因,并尽量提供相关凭证图片,点击提交申请.最大可退金额为您的实付金额:   3.退款申请提交后,需要等待商家处理,商家同意退款后,系统后续会自动发起退款.

Android 仿QQ头像自定义截取功能_Android

看了Android版QQ的自定义头像功能,决定自己实现,随便熟悉下android绘制和图片处理这一块的知识. 先看看效果: 思路分析: 这个效果可以用两个View来完成,上层View是一个遮盖物,绘制半透明的颜色,中间挖了一个圆:下层的View用来显示图片,具备移动和缩放的功能,并且能截取某区域内的图片. 涉及到的知识点: 1.Matrix,图片的移动和缩放 2.Paint的setXfermode方法 3.图片放大移动后,截取一部分 编码实现: 自定义三个View: 1.下层View:ClipP

拼多多APP怎么投诉 拼多多APP投诉商家方法解析

给各位拼多多软件的使用者们来详细的解析分享一下拼多多投诉商家的方法. 解析分享:    买家申请退款后,若卖家拒绝了退款协议且相关交易仍在进行中,买家可以操作"修改退款协议",若双方无法协商一致,买家可以在卖家拒绝退款后(虚拟物品:卖家拒绝退款后:申请退款3天后两个条件同时满足)点击退款详情页的"要求客服介入"按钮,要求淘宝小二来帮助协商处理,具体操作如下:   第一步:   进入退款管理页面,找到对应的交易查看退款详情,在退款详情页面提醒规定时间内及时点击&quo