Android从零开搞系列:自定义View(10)流式布局
来源:互联网 发布:目前常见数据库有哪些 编辑:程序博客网 时间:2024/05/20 04:28
转载请注意:http://blog.csdn.net/wjzj000/article/details/65936007
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)
写在前面
这段时间被国足踢赢韩国刷屏,确实很解气。天朝有一百种方式赢棒子,非逼我们用这种方式出手…
这次博客记录一个自定义的ViewGroup,比较常见的效果:流式布局。
简单效果如下:
此效果来自开源库:https://github.com/crazyandcoder/MultiLineChoose
关于使用,各位看官感兴趣可以移步到大神的GitHub上一睹王者之霸气。本篇博客是我在看大神源码过程之中的总结和记录。
开始
针对这种布局,我们可以想到:继承ViewGroup重写onLayout方法,计算子控件的宽度,如果大于父控件的宽度,那我们就让子控件在下一行进行layout。Ok,让我们带着思路来看一下大神的源码是怎么做的。
首先如我们所想:
继承了ViewGroup
public class MultiLineChooseLayout extends ViewGroup
紧接着就是正常的在构造方法中完成对自定义属性的初始化:
final TypedArray attrsArray = context.obtainStyledAttributes(attrs,R.styleable.MultiLineChooseItemTags,defStyleAttr,R.style.MultiLineChooseItemTags);textColor = attrsArray.getColor(R.styleable.MultiLineChooseItemTags_item_textColor,default_text_color);backgroundColor =attrsArray.getColor(R.styleable.MultiLineChooseItemTags_item_backgroundColor,default_background_color);selectedTextColor =attrsArray.getColor(R.styleable.MultiLineChooseItemTags_item_selectedTextColor,default_checked_text_color);//省略部分初始化
onMeasure方法:
在这里我们需要小小的注意一下。onMeasure之中我们在处理warp_content
时要考虑子View各个位置的情况,因为子View有可能排列时会大于一行,也有可能不足一行。
源码如下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); final int widthSize = MeasureSpec.getSize(widthMeasureSpec); final int heightSize = MeasureSpec.getSize(heightMeasureSpec); //调用此方法后,我们可以获取通过子View的getMeasuredWidth/getMeasuredHeight获取子View的宽高信息。 measureChildren(widthMeasureSpec, heightMeasureSpec); int width = 0; int height = 0; int row = 0; // The row counter. int rowWidth = 0; // Calc the current row width. int rowMaxHeight = 0; // Calc the max tag height, in current row. final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); if (child.getVisibility() != GONE) { rowWidth += childWidth; if (rowWidth > widthSize) { // 下一行 rowWidth = childWidth; // 下一行宽度。 height += rowMaxHeight + verticalSpacing; rowMaxHeight = childHeight; // 下一行最大高度。 row++; } else { // 这一行。 rowMaxHeight = Math.max(rowMaxHeight, childHeight); } rowWidth += horizontalSpacing; } } height += rowMaxHeight; height += getPaddingTop() + getPaddingBottom(); if (row == 0) { //只有一行item width = rowWidth; width += getPaddingLeft() + getPaddingRight(); } else { // 如果分组的标签超过一行,请设置宽度以匹配父级。 width = widthSize; } setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, heightMode == MeasureSpec.EXACTLY ? heightSize : height); }
上诉的代码都是套路性的东西。通过遍历子View来计算是否占够一行的宽度,如果够那么就下一行。并且设置父View的宽度为行宽;否则便是子View一共多宽父View就有多宽。
onLayout方法:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //设置父View的起始和终止点 final int parentLeft = getPaddingLeft(); final int parentRight = r - l - getPaddingRight(); final int parentTop = getPaddingTop(); final int parentBottom = b - t - getPaddingBottom(); int childLeft = parentLeft; int childTop = parentTop; int rowMaxHeight = 0; final int count = getChildCount(); //遍历子View进行,计算宽高,然后调用layout for (int i = 0; i < count; i++) { final View child = getChildAt(i); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); if (child.getVisibility() != GONE) { // 如果当前View的位置大于父View的宽度,那么就放置到下一行 if (childLeft + width > parentRight) { childLeft = parentLeft; childTop += rowMaxHeight + verticalSpacing; rowMaxHeight = height; } else { rowMaxHeight = Math.max(rowMaxHeight, height); } child.layout(childLeft, childTop, childLeft + width, childTop + height); childLeft += width + horizontalSpacing; } } }
Ok,截止到这,我们可以在MultiLineChooseLayout这个布局之中加入子View进行正常的流式显示。但是我们需要的是动态的添加子View,因此仅有这些是肯定不够的。
动态添加子View
我们正常使用的时候是这样色的:
List<String> mDataList = new ArrayList<>();mDataList.add("赵云");mDataList.add("关羽");mDataList.add("张飞");mDataList.add("黄忠");mDataList.add("马超");mDataList.add("吕布");mDataList.add("高顺");mDataList.add("张辽");mDataList.add("诸葛亮");singleChoose.setList(mDataList);
通过使用我们可以看出来,我们通过MultiLineChooseLayout的setList方法进行动态设置显示内容。
接下来让我们看代码:
public void setList(List<String> tagList) { setList(tagList.toArray(new String[tagList.size()]));}//setListpublic void setList(String... tags) { removeAllViews(); for (final String tag : tags) { addItem(tag); }}//addItemprivate void addItem(CharSequence tag) { final ItemView item = new ItemView(getContext(), tag); item.setOnClickListener(mInternalTagClickListener); //此处,通过new自定义的ItemView,然后设置我们setList中的值。调用addView传入这个对象 addView(item);}
ItemView:
class ItemView extends TextView//构造方法之中,除了进行一些初始化以为。就属这行代码比较特殊setLayoutParams(new MultiLineChooseLayout.LayoutParams(itemWidth, itemHeight));
可以看出,MultiLineChooseLayout.LayoutParams仅仅是继承了ViewGroup.LayoutParams的一个类而已,并未做特殊处理。
public static class LayoutParams extends ViewGroup.LayoutParams { public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); }}
接下来我们就看一下onDraw方法:
@Overrideprotected void onDraw(Canvas canvas) { //在正常绘制TextView之前,我们要进行一些自己的绘制处理 if (!animUpdateDrawable) { updateDrawable(); } super.onDraw(canvas);}//updateDrawable()private void updateDrawable() { //进行画框 mStrokeColor = mStrokeColor == null ? ColorStateList.valueOf(Color.TRANSPARENT) : mStrokeColor; mCheckedStrokeColor = mCheckedStrokeColor == null ? mStrokeColor : mCheckedStrokeColor; updateDrawable(!isChecked ? mStrokeColor.getDefaultColor() : mCheckedStrokeColor.getDefaultColor());}//updateDrawable(int strokeColor)private void updateDrawable(int strokeColor) { int mbackgroundColor; if (isChecked) { mbackgroundColor = selectedBackgroundColor; } else { mbackgroundColor = backgroundColor; } GradientDrawable drawable = new GradientDrawable(); drawable.setCornerRadii(mRadius); drawable.setColor(mbackgroundColor); drawable.setStroke(mStrokeWidth, strokeColor); if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { this.setBackgroundDrawable(drawable); } else { this.setBackground(drawable); }}
然后就是onTouchEvent()方法:
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { getDrawingRect(mOutRect); invalidatePaint(); invalidate(); break; } case MotionEvent.ACTION_MOVE: { if (!mOutRect.contains((int) event.getX(), (int) event.getY())) { invalidatePaint(); invalidate(); } break; } case MotionEvent.ACTION_UP: { invalidatePaint(); invalidate(); break; } } return super.onTouchEvent(event); } //invalidatePaint() private void invalidatePaint() { animUpdateDrawable = false; if (isChecked) { mBackgroundPaint.setColor(selectedBackgroundColor); setTextColor(selectedTextColor); } else { mBackgroundPaint.setColor(backgroundColor); setTextColor(textColor); } }
到此我们自定义的这个字View就被添加到了父View之中,说白了就是addView的功能。当然我们也可以使用LayoutInflate来构建自己的子View。
监听事件
监听事件的使用也和我们正常写回调的方式没有什么不同,而且我们在分析addView的时候我们就已经见到了监听的使用。
private void addItem(CharSequence tag) { final ItemView item = new ItemView(getContext(), tag); item.setOnClickListener(mInternalTagClickListener); addView(item);}
让我们来看一看ItemClicker的代码是怎么写的:
public void setOnItemClickListener(onItemClickListener l) { mOnItemClickLisener = l; } class ItemClicker implements OnClickListener { @Override public void onClick(View v) { final ItemView tag = (ItemView) v; int position = -1; //getSelectedItem()方法会通过遍历所有的ItemView中的isChecked来拿到被选中的ItemView final ItemView checkedTag = getSelectedItem(); if (!multiChooseable) { //单选 if (checkedTag != null) { checkedTag.setItemSelected(false); } tag.setItemSelected(true); position = getSelectedIndex(); } else { //多选 tag.setItemSelected(!tag.isChecked); position = -1; } //外部点击事件,完成回调 if (mOnItemClickLisener != null) { mOnItemClickLisener.onItemClick(position, tag.getText().toString()); } } }
而我们使用的时候,就可以直接set
singleChoose.setOnItemClickListener(new MultiLineChooseLayout.onItemClickListener() { @Override public void onItemClick(int position, String text) { singleChooseTv.setText("结果:position: " + position + " " + text); } });
尾声
Ok,整体View的思路就是如此,当然我们还需要处理细节之处,因为整个项目是开源的。因此各位看官如果有特殊需求服务….移步大神的GitHub。
最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp
- Android从零开搞系列:自定义View(10)流式布局
- Android从零开搞系列:自定义View(1)setContent()台前幕后
- Android从零开搞系列:自定义View(8)Canvas解析
- android 自定义view 流式布局
- android 自定义view实现流式布局
- Android自定义View实现流式布局
- Android从零开搞系列:自定义View(16)自定义验证码输入框效果
- Android从零开搞系列:自定义View(6)ScrollTo+ScrollBy+Scroller+NestedScrolling机制(上)
- Android从零开搞系列:自定义View(7)ScrollTo+ScrollBy+Scroller+NestedScrolling机制(下)
- Android从零开搞系列:自定义View(9)事件分发+事件拦截(滑动冲突)
- Android从零开搞系列:自定义View(2)继承RecyclerView实现下拉刷新和加载更多
- Android从零开搞系列:自定义View(3)Canvas基本API+综合应用+开源分析
- Android从零开搞系列:自定义View(11)使用ViewPager打造轮播广告条
- Android从零开搞系列:自定义View(12)贝塞尔曲线的应用
- Android从零开搞系列:自定义View(13)新消息小圆点效果
- Android从零开搞系列:自定义View(4)基本的自定义ViewPager指示器+开源项目分析(上)
- Android从零开搞系列:自定义View(5)基本的自定义ViewPager指示器+开源项目分析(下)
- Android从零开搞系列:动画系列(1)View动画
- 1029. 旧键盘(20) python篇
- linux下阻挡ssh暴力破解
- 11. Container With Most Water
- Android 图片加载框架Universal-Image-Loader源码解析
- 如何判断文档或文件夹为空?
- Android从零开搞系列:自定义View(10)流式布局
- 获取综合教务系统(URP)的数据包
- 传智播客C++第五/5期完整版
- autodesk 安装
- 257. Binary Tree Paths
- Android IPC机制(一)
- FFmpeg 初始化
- Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: 索引 1 超出范围
- 关于Android使用socket与PC连接的问题