前言
在一些APP中我们可以看到一些存放标签的容器控件,和我们平时使用的一些布局方式有些不同,它们一般都可以自动适应屏幕的宽度进行布局,根据对自定义控件的一些理解,今天写一个简单的标签容器控件,给大家参考学习。
下面这个是我在手机上截取的一个实例,是在Miui8系统上截取的
这个是我实现的效果图
原理介绍
根据对整个控件的效果分析,大致可以将控件分别从以下这几个角度进行分析:
1.首先涉及到自定义的VIEwGroup,因为现有的控件没法满足我们的布局效果,就涉及到要重写onMeasure和onLayout,这里需要注意的问题是自定义view的时候,我们需要考虑到VIEw的padding属性,而在自定义viewGroup中我们需要在onLayout中考虑Child控件的margin属性否则子类设置这个属性将会失效。整个VIEw的绘制流程是这样的:
最顶层的VIEwRoot执行performTraversals然后分别开始对各个VIEw进行层级的测量、布局、绘制,整个流程是一层一层进行的,也就是说父视图测量时会调用子视图的测量方法,子视图调孙视图方法,一直测量到叶子节点,performTraversals这个函数翻译过来很直白,执行遍历,就说明了这种层级关系。
2.该控件形式上和ListVIEw的形式比较相近,所以在这里我也模仿ListVIEw的Adapter模式实现了对控件内容的 *** 作,这里对ListVIEw的setAdapter和Adapter的notifyDataSetChanged方法做个简单的解释:
在ListVIEw调用setAdapter后,ListVIEw会去注册一个Observer对象到这个adapter上,然后当我们在改变设置到adapter上的数据发改变时,我们会调用adapter的notifyDataSetChanged方法,这个方法就会通知所有监听了该Adapter数据改变时的Observer对象,这就是典型的监听者模式,这时由于ListVIEw中的内部成员对象监听了该事件,就可以知道数据源发生了改变,我们需要对真个控件重新进行绘制了,下面来一些相关的源码。
Adapter的notifyDataSetChanged
public voID notifyDataSetChanged() { mDataSetobservable.notifyChanged(); }
ListVIEw的setAdapter方法
@OverrIDe public voID setAdapter(listadapter adapter) { /** *每次设置新的适配的时候,如果现在有的话会做一个解除监听的 *** 作 */ if (mAdapter != null && mDataSetobserver != null) { mAdapter.unregisterDataSetobserver(mDataSetobserver); } resetList(); mRecycler.clear(); /** 省略部分代码..... */ if (mAdapter != null) { mAreAllitemsSelectable = mAdapter.areAllitemsEnabled(); moldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); /** *在这里对adapter设置了监听, *使用的是AdapterDataSetobserver类的对象,该对象定义在ListVIEw的父类AdapterVIEw中 */ mDataSetobserver = new AdapterDataSetobserver(); mAdapter.registerDataSetobserver(mDataSetobserver); /** 省略 */ } else { /** 省略 */ } requestLayout(); }
AdapterVIEw中的内部类AdapterDataSetobserver
class AdapterDataSetobserver extends DataSetobserver { private Parcelable mInstanceState = null; @OverrIDe public voID onChanged() { /* ***代码略*** */ checkFocus(); requestLayout(); } @OverrIDe public voID onInvalIDated() { /* ***代码略*** */ checkFocus(); requestLayout(); } public voID clearSavedState() { mInstanceState = null; } }
一段伪代码表示
ListVIEw{ Observer observer{ onChange(){ change; } } setAdapter(Adapter adapter){ adapter.register(observer); }}Adapter{ List<Observer> mObservable; register(observer){ mObservable.add(observer); } notifyDataSetChanged(){ for(i-->mObserverable.size()){ mObserverable.get(i).onChange } }}
实现过程
获取VIEwItem的接口
package humoursz.grIDtag.test.adapter;import androID.vIEw.VIEw;import java.util.List;/** * Created by zhangzhiquan on 2016/7/19. */public interface GrIDeTagBaseAdapter { List<VIEw> getVIEws();}
抽象适配器AbsGrIDTagsAdapter
package humoursz.grIDtag.test.adapter;import androID.database.DataSetobservable;import androID.database.DataSetobserver;/** * Created by zhangzhiquan on 2016/7/19. */public abstract class AbsGrIDTagsAdapter implements GrIDeTagBaseAdapter { DataSetobservable mObservable = new DataSetobservable(); public voID notification(){ mObservable.notifyChanged(); } public voID registerObserve(DataSetobserver observer){ mObservable.registerObserver(observer); } public voID unregisterObserve(DataSetobserver observer){ mObservable.unregisterObserver(observer); }}
此效果中的需要的适配器,实现了getVIEw接口,主要是模仿了ListVIEw的BaseAdapter
package humoursz.grIDtag.test.adapter;import androID.content.Context;import androID.vIEw.LayoutInflater;import androID.vIEw.VIEw;import androID.Widget.TextVIEw;import java.util.ArrayList;import java.util.List;import humoursz.grIDtag.test.R;import humoursz.grIDtag.test.util.UIUtil;import humoursz.grIDtag.test.Widget.GrIDTagVIEw;/** * Created by zhangzhiquan on 2016/7/19. */public class MyGrIDTagAdapter extends AbsGrIDTagsAdapter { private Context mContext; private List<String> mTags; public MyGrIDTagAdapter(Context context,List<String> Tags) { mContext = context; mTags = Tags; } @OverrIDe public List<VIEw> getVIEws() { List<VIEw> List = new ArrayList<>(); for (int i = 0; i < mTags.size(); i++) { TextVIEw tv = (TextVIEw) LayoutInflater.from(mContext) .inflate(R.layout.grID_tag_item_text,null); tv.setText(mTags.get(i)); GrIDTagVIEw.LayoutParams lp = new GrIDTagVIEw .LayoutParams(GrIDTagVIEw.LayoutParams.WRAP_CONTENT,GrIDTagVIEw.LayoutParams.WRAP_CONTENT); lp.margin(UIUtil.dp2px(mContext,5)); tv.setLayoutParams(lp); List.add(tv); } return List; }}
最后是主角GrIDTagsVIEw控件
package humoursz.grIDtag.test.Widget;import androID.content.Context;import androID.database.DataSetobserver;import androID.util.AttributeSet;import androID.util.Log;import androID.vIEw.VIEw;import androID.vIEw.VIEwGroup;import java.util.List;import humoursz.grIDtag.test.adapter.AbsGrIDTagsAdapter;/** * Created by zhangzhiquan on 2016/7/18. */public class GrIDTagVIEw extends VIEwGroup { private int mlines = 1; private int mWIDthSize = 0; private AbsGrIDTagsAdapter mAdapter; private GTObserver mObserver = new GTObserver(); public GrIDTagVIEw(Context context) { this(context,null); } public GrIDTagVIEw(Context context,AttributeSet attrs) { this(context,attrs,0); } public GrIDTagVIEw(Context context,AttributeSet attrs,int defStyleAttr) { super(context,defStyleAttr); } public voID setAdapter(AbsGrIDTagsAdapter adapter) { if (mAdapter != null) { mAdapter.unregisterObserve(mObserver); } mAdapter = adapter; mAdapter.registerObserve(mObserver); mAdapter.notification(); } @OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { int wIDthSize = MeasureSpec.getSize(wIDthMeasureSpec); int heightSize = MeasureSpec.getSize(heightmeasureSpec); int curWIDthSize = 0; int childHeight = 0; mlines = 1; for (int i = 0; i < getChildCount(); ++i) { VIEw child = getChildAt(i); measureChild(child,wIDthMeasureSpec,heightmeasureSpec); curWIDthSize += getChildRealWIDthSize(child); if (curWIDthSize > wIDthSize) { /** * 计算一共需要多少行,用于计算控件的高度 * 计算方法是,如果当前控件放下后宽度超过 * 容器本身的高度,就放到下一行 */ curWIDthSize = getChildRealWIDthSize(child); mlines++; } if (childHeight == 0) { /** * 在第一次计算时拿到字视图的高度作为计算基础 */ childHeight = getChildRealHeightSize(child); } } mWIDthSize = wIDthSize; setMeasuredDimension(wIDthSize,childHeight == 0 ? heightSize : childHeight * mlines); } @OverrIDe protected voID onLayout(boolean changed,int l,int t,int r,int b) { if (getChildCount() == 0) return; int childCount = getChildCount(); LayoutParams lp = getChildLayoutParams(getChildAt(0)); /** * 初始的左边界在自身的padding left和child的margin后 * 初始的上边界原理相同 */ int left = getpaddingleft() + lp.leftmargin; int top = getpaddingtop() + lp.topmargin; int curleft = left; for (int i = 0; i < childCount; ++i) { VIEw child = getChildAt(i); int right = curleft + getChildRealWIDthSize(child); /** * 计算如果放下当前试图后整个一行到右侧的距离 * 如果超过控件宽那就放到下一行,并且左边距还原,上边距等于下一行的开始 */ if (right > mWIDthSize) { top += getChildRealHeightSize(child); curleft = left; } child.layout(curleft,top,curleft + child.getMeasureDWIDth(),top + child.getMeasuredHeight()); /** * 下一个控件的左边开始距离是上一个控件的右边 */ curleft += getChildRealWIDthSize(child); } } /** * 获取childVIEw实际占用宽度 * @param child * @return 控件实际占用的宽度,需要算上margin否则margin不生效 */ private int getChildRealWIDthSize(VIEw child) { LayoutParams lp = getChildLayoutParams(child); int size = child.getMeasureDWIDth() + lp.leftmargin + lp.rightmargin; return size; } /** * 获取childVIEw实际占用高度 * @param child * @return 实际占用高度需要考虑上下margin */ private int getChildRealHeightSize(VIEw child) { LayoutParams lp = getChildLayoutParams(child); int size = child.getMeasuredHeight() + lp.topmargin + lp.bottommargin; return size; } /** * 获取LayoutParams属性 * @param child * @return */ private LayoutParams getChildLayoutParams(VIEw child) { LayoutParams lp; if (child.getLayoutParams() instanceof LayoutParams) { lp = (LayoutParams) child.getLayoutParams(); } else { lp = (LayoutParams) generateLayoutParams(child.getLayoutParams()); } return lp; } @OverrIDe public VIEwGroup.LayoutParams generateLayoutParams(AttributeSet attr) { return new LayoutParams(getContext(),attr); } @OverrIDe protected VIEwGroup.LayoutParams generateLayoutParams(VIEwGroup.LayoutParams p) { return new LayoutParams(p); } public static class LayoutParams extends marginLayoutParams { public LayoutParams(Context c,AttributeSet attrs) { super(c,attrs); } public LayoutParams(int wIDth,int height) { super(wIDth,height); } public LayoutParams(marginLayoutParams source) { super(source); } public LayoutParams(VIEwGroup.LayoutParams source) { super(source); } public voID marginleft(int left) { this.leftmargin = left; } public voID marginRight(int r) { this.rightmargin = r; } public voID margintop(int t) { this.topmargin = t; } public voID marginBottom(int b) { this.bottommargin = b; } public voID margin(int m){ this.leftmargin = m; this.rightmargin = m; this.topmargin = m; this.bottommargin = m; } } private class GTObserver extends DataSetobserver { @OverrIDe public voID onChanged() { removeAllVIEws(); List<VIEw> List = mAdapter.getVIEws(); for (int i = 0; i < List.size(); i++) { addVIEw(List.get(i)); } } @OverrIDe public voID onInvalIDated() { Log.d("Mrz","fd"); } }}
MainActivity
package humoursz.grIDtag.test;import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.vIEw.VIEw;import java.util.List;import humoursz.grIDtag.test.adapter.MyGrIDTagAdapter;import humoursz.grIDtag.test.util.ListUtil;import humoursz.grIDtag.test.Widget.GrIDTagVIEw;public class MainActivity extends AppCompatActivity { MyGrIDTagAdapter adapter; GrIDTagVIEw mGrIDTag; List<String> mList; @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); mGrIDTag = (GrIDTagVIEw)findVIEwByID(R.ID.grID_Tags); mList = ListUtil.getGrIDTagsList(20); adapter = new MyGrIDTagAdapter(this,mList); mGrIDTag.setAdapter(adapter); } public voID onClick(VIEw v){ mList.removeAll(mList); mList.addAll(ListUtil.getGrIDTagsList(20)); adapter.notification(); }}
XML 文件
<?xml version="1.0" enCoding="utf-8"?><relativeLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" xmlns:tools="http://schemas.androID.com/tools" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" tools:context="humoursz.grIDtag.test.MainActivity"> <humoursz.grIDtag.test.Widget.GrIDTagVIEw androID:ID="@+ID/grID_Tags" androID:layout_wIDth="match_parent" androID:layout_height="wrap_content"> </humoursz.grIDtag.test.Widget.GrIDTagVIEw> <button androID:layout_centerInParent="true" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:onClick="onClick" androID:text="换一批"/></relativeLayout>
以上就是AndroID中标签容器控件的全部实现过程,这样一个简单的控件就写好了,主要需要注意measure和layout否则很多效果都会失效,安卓中的linearLayout之类的控件实际实现起来要复杂的很多,因为支持的属性实在的太多了,多动手实践可以帮助理解,希望本文能帮助到在AndroID开发中的大家。
总结以上是内存溢出为你收集整理的Android中标签容器控件的实例详解全部内容,希望文章能够帮你解决Android中标签容器控件的实例详解所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)