Android仿拼多多拼团堆叠头像
来源:互联网 发布:没有网络的游戏 编辑:程序博客网 时间:2024/04/28 09:29
序言
做电商的都知道,作为商品的一种售卖方式,拼团是是提供商品售卖的一种及时有效的方式,而在拼团市场中,拼多多无疑是做的最好的一家。于是,研究拼多多的售卖方式之后,我们的产品也开始了这方面的开发。本文将要给大家介绍的就是通过自定义的方式实现堆叠头像,这种效果在直播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" />
- Android仿拼多多拼团堆叠头像
- Android仿拼多多拼团堆叠头像
- 解密微信拼团电商:拼多多
- 拼多多面试题
- 拼多多面试题
- 拼多多笔试
- 拼多多笔试题
- 拼多多笔试题
- 2018 拼多多
- 拼多多面试
- 拼多多面试2
- 拼多多笔试总结
- 拼多多笔试
- 拼
- 拼多多前端笔试题
- 拼多多笔试题总结
- 面试编程题拼多多
- 拼多多拼团小程序开发
- 【第5周】项目2-建立链栈算法库
- linux初学之管理网络
- 设计模式六大原则总结------备忘
- windows xp home edition 可用序列号
- Android输入事件从读取到分发五:事件分发前的拦截过程
- Android仿拼多多拼团堆叠头像
- 排序--面经
- springmvc下载文件弹出框
- httpd服务启用压缩功能
- bzoj1057 [ZJOI2007]棋盘制作
- 部分读和部分写的原因及处理方法
- tcp_tw_reuse、tcp_tw_recycle 使用场景及注意事项
- 笔记-CART
- 第七周-项目3