前段時間,項目裏要做一個熱搜詞的頁面,當時隨便在網上找了一個改了改就拿來用了。這幾天稍微有點空閒,想辦法再來改善改善。
這是改完後的效果:
一個個標籤都是單獨的TextView,外層用ViewGroup包裹住。
首先,新建一個自定義View繼承ViewGroup
然後爲TextView 新建兩個不同背景的drawable
默認狀態的:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#ffffff"/> <corners android:radius="4dp"/> <stroke android:color="#aeaeae" android:width="2px"/> </shape>選中狀態的:
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#aaaaaa"/> <corners android:radius="4dp"/> <stroke android:color="#aeaeae" android:width="2px"/> </shape>
之前是沒有任何的點擊事件的,我加了三種模式:
public static final int COMMON_MODE = 1;//普通模式 標籤可供點擊 有點擊事件 public static final int SINGLE_CHOICE_MODE = 2;//單選模式 public static final int MULTIPLE_CHOICE_MODE = 3;//多選模式
聲明接口
public interface ClickListener { public void click(int position); //點擊後觸發 public void check(int position);//選中後觸發 public void unCheck(int position);//取消選中觸發 }
在新建一個內部類來存儲子View的位置
private class ChildPos { int left, top, right, bottom; public ChildPos(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } }
需要重寫onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //獲取流式佈局的寬度和模式 int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取流式佈局的高度和模式 int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //使用wrap_content的流式佈局的最終寬度和高度 int width = 0, height = 0; //記錄每一行的寬度和高度 int lineWidth = 0, lineHeight = 0; //得到內部元素的個數 int count = getChildCount(); mChildPos.clear(); for (int i = 0; i < count; i++) { //獲取對應索引的view View child = getChildAt(i); //測量子view的寬和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //子view佔據的寬度 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; //子view佔據的高度 int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; if (lineWidth + childWidth > widthSize - getPaddingLeft() - getPaddingRight()) { //取最大的行寬爲流式佈局寬度 width = Math.max(width, lineWidth); //疊加行高得到流式佈局高度 height += lineHeight; //重置行寬度爲第一個View的寬度 lineWidth = childWidth; //重置行高度爲第一個View的高度 lineHeight = childHeight; mChildPos.add(new ChildPos(getPaddingLeft() + lp.leftMargin, getPaddingTop() + height + lp.topMargin, getPaddingLeft() + childWidth - lp.rightMargin, getPaddingTop() + height + childHeight - lp.bottomMargin)); } else { mChildPos.add(new ChildPos(getPaddingLeft() + lineWidth + lp.leftMargin, getPaddingTop() + height + lp.topMargin, getPaddingLeft() + lineWidth + childWidth - lp.rightMargin, getPaddingTop() + height + childHeight - lp.bottomMargin)); //疊加子View寬度得到新行寬度 lineWidth += childWidth; //取當前行子View最大高度作爲行高度 lineHeight = Math.max(lineHeight, childHeight); } //最後一個控件 if (i == count - 1) { width = Math.max(lineWidth, width); height += lineHeight; } } setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width + getPaddingLeft() + getPaddingRight(), heightMode == MeasureSpec.EXACTLY ? heightSize : height + getPaddingTop() + getPaddingBottom()); }
初始化方法: 傳入一個TextView的layout id,一個數據源,還有模式
public void init(int resource,List<String> datas,int mode){ this.mLayoutId = resource; this.mDatas.addAll(datas); setNowMode(mode); initTextView(); }
//接下來進行數據裝配,並通過checkList來保存該view的狀態
public void initTextView() { if(mDatas.size()>0){ checkList.clear(); for(int i=0;i<mDatas.size();i++){ TextView tv = (TextView) LayoutInflater.from(mContext) .inflate(mLayoutId, WallFlowView.this, false); tv.setText(mDatas.get(i).toString()); checkList.add(false); addView(tv); }
refreshView();
} }
刷新UI:
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public void refreshView(){ if(getNowMode() != COMMON_MODE){// if(checkList.size()>0){ for(int i=0;i<checkList.size();i++){ TextView view = (TextView) getChildAt(i); if(checkList.get(i)){ view.setBackgroundDrawable(mContext.getDrawable(R.drawable.wall_flow_text_checked_background_layout)); }else{ view.setBackgroundDrawable(mContext.getDrawable(R.drawable.wall_flow_text_background_layout)); } } } } }
大致就是以上一些核心的方法了,最後再在點擊事件裏面做模式分發
@Override public void onClick(View view) { TextView textView = (TextView) view; int position = (int) textView.getTag(); if(getNowMode() == COMMON_MODE){ clickListener.click(position); }else if(getNowMode() == SINGLE_CHOICE_MODE){ if(checkList.get(position)){ checkList.set(position,false); clickListener.unCheck(position); }else{ resetCheckList(); checkList.set(position,true); clickListener.check(position); } refreshView(); }else{ if(checkList.get(position)){ checkList.set(position,false); clickListener.unCheck(position); }else{ checkList.set(position,true); clickListener.check(position); } refreshView(); } }
再貼上我的activity
public class WallFlowActivity extends BaseActivity{ @Bind(R.id.wfv_tag) WallFlowView mWfvView; @Bind(R.id.et) EditText mEt; @Bind(R.id.add) Button mBtn; private List<String> list = new ArrayList<>(); @Override protected void onActivityCreate(Bundle savedInstanceState) { super.onActivityCreate(savedInstanceState); setContentView(R.layout.activity_wall_flow); ButterKnife.bind(this); } @Override protected void initView() { super.initView(); list.add("當時我念了兩句詩"); list.add("我是友軍"); list.add("二營長,我的意大利炮呢?"); list.add("怪我咯"); list.add("666666"); list.add("午時已到"); list.add("嘿嘿嘿"); list.add("天下第二"); list.add("污妖王"); mWfvView.init(R.layout.item_tag_text,list,WallFlowView.MULTIPLE_CHOICE_MODE); } @Override protected void initListener() { super.initListener(); mWfvView.setClickListener(new WallFlowView.ClickListener() { @Override public void click(int position) { Toast.makeText(WallFlowActivity.this,position+"click",Toast.LENGTH_SHORT).show(); } @Override public void check(int position) { Toast.makeText(WallFlowActivity.this,position+"check",Toast.LENGTH_SHORT).show(); } @Override public void unCheck(int position) { Toast.makeText(WallFlowActivity.this,position+"unCheck",Toast.LENGTH_SHORT).show(); } }); mBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mEt.getText().toString().trim().length()>0){ list.add(mEt.getText().toString()); mWfvView.addTextView(mEt.getText().toString()); Toast.makeText(WallFlowActivity.this,"添加新標籤"+mEt.getText().toString(),Toast.LENGTH_SHORT).show(); } } }); } @Override protected void initData() { super.initData(); }
自定義View代碼全部貼一遍:
/** * 作者:dls on 2016年10月9日10:16:35 * 郵箱:[email protected] * 版本:1.0.0 * 流式標籤View 應用場景 熱搜詞 多詞篩選 */ public class WallFlowView extends ViewGroup implements View.OnClickListener { private List<String> mDatas = new ArrayList<>(); public static final int COMMON_MODE = 1; public static final int SINGLE_CHOICE_MODE = 2; public static final int MULTIPLE_CHOICE_MODE = 3; //記錄每個View的位置 private List<ChildPos> mChildPos = new ArrayList<ChildPos>(); private ClickListener clickListener; private int mNowMode = COMMON_MODE; private int mLayoutId; private Context mContext; //記錄條目選中狀態 public List<Boolean> checkList = new ArrayList<>(); /** * 點擊事件接口 */ public interface ClickListener { public void click(int position); public void check(int position); public void unCheck(int position); } /** * 每個子控件的點擊事件 * * @param clickListener */ public void setClickListener(ClickListener clickListener) { this.clickListener = clickListener; } @Override public void onClick(View view) { TextView textView = (TextView) view; int position = (int) textView.getTag(); if(getNowMode() == COMMON_MODE){ clickListener.click(position); }else if(getNowMode() == SINGLE_CHOICE_MODE){ if(checkList.get(position)){ checkList.set(position,false); clickListener.unCheck(position); }else{ resetCheckList(); checkList.set(position,true); clickListener.check(position); } refreshView(); }else{ if(checkList.get(position)){ checkList.set(position,false); clickListener.unCheck(position); }else{ checkList.set(position,true); clickListener.check(position); } refreshView(); } } private class ChildPos { int left, top, right, bottom; public ChildPos(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } } public WallFlowView(Context context) { this(context, null); this.mContext = context; } public WallFlowView(Context context, AttributeSet attrs) { this(context, attrs, 0); this.mContext = context; } /** * 最終調用這個構造方法 * * @param context 上下文 * @param attrs xml屬性集合 * @param defStyle Theme中定義的style */ public WallFlowView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.mContext = context; } /** * 測量寬度和高度 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //獲取流式佈局的寬度和模式 int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取流式佈局的高度和模式 int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //使用wrap_content的流式佈局的最終寬度和高度 int width = 0, height = 0; //記錄每一行的寬度和高度 int lineWidth = 0, lineHeight = 0; //得到內部元素的個數 int count = getChildCount(); mChildPos.clear(); for (int i = 0; i < count; i++) { //獲取對應索引的view View child = getChildAt(i); //測量子view的寬和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //子view佔據的寬度 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; //子view佔據的高度 int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; //換行 if (lineWidth + childWidth > widthSize - getPaddingLeft() - getPaddingRight()) { //取最大的行寬爲流式佈局寬度 width = Math.max(width, lineWidth); //疊加行高得到流式佈局高度 height += lineHeight; //重置行寬度爲第一個View的寬度 lineWidth = childWidth; //重置行高度爲第一個View的高度 lineHeight = childHeight; //記錄位置 mChildPos.add(new ChildPos(getPaddingLeft() + lp.leftMargin, getPaddingTop() + height + lp.topMargin, getPaddingLeft() + childWidth - lp.rightMargin, getPaddingTop() + height + childHeight - lp.bottomMargin)); } else { //不換行 //記錄位置 mChildPos.add(new ChildPos(getPaddingLeft() + lineWidth + lp.leftMargin, getPaddingTop() + height + lp.topMargin, getPaddingLeft() + lineWidth + childWidth - lp.rightMargin, getPaddingTop() + height + childHeight - lp.bottomMargin)); //疊加子View寬度得到新行寬度 lineWidth += childWidth; //取當前行子View最大高度作爲行高度 lineHeight = Math.max(lineHeight, childHeight); } //最後一個控件 if (i == count - 1) { width = Math.max(lineWidth, width); height += lineHeight; } } setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width + getPaddingLeft() + getPaddingRight(), heightMode == MeasureSpec.EXACTLY ? heightSize : height + getPaddingTop() + getPaddingBottom()); } /** * 讓ViewGroup能夠支持margin屬性 */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } /** * 設置每個View的位置 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); child.setOnClickListener(this); child.setTag(i); ChildPos pos = mChildPos.get(i); //設置View的左邊、上邊、右邊底邊位置 child.layout(pos.left, pos.top, pos.right, pos.bottom); Log.e("test","view"+i+"left"+pos.left+"top"+pos.top+"right"+ pos.right+"bottom"+pos.bottom); } } /** * 初始化 * @param resource * @param datas * @param mode */ public void init(int resource,List<String> datas,int mode){ this.mLayoutId = resource; this.mDatas.addAll(datas); setNowMode(mode); initTextView(); } /** * 順序添加數據 * */ public void initTextView() { if(mDatas.size()>0){ checkList.clear(); for(int i=0;i<mDatas.size();i++){ TextView tv = (TextView) LayoutInflater.from(mContext) .inflate(mLayoutId, WallFlowView.this, false); tv.setText(mDatas.get(i).toString()); checkList.add(false); addView(tv); refreshView(); } } } /** * 單獨添加數據 * @param tvName */ public void addTextView(String tvName) { TextView tv = (TextView) LayoutInflater.from(mContext) .inflate(mLayoutId, WallFlowView.this, false); tv.setText(tvName); mDatas.add(tvName); checkList.add(false); addView(tv); refreshView(); } /** * 刷新數據 */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void refreshView(){ if(getNowMode() != COMMON_MODE){// if(checkList.size()>0){ for(int i=0;i<checkList.size();i++){ TextView view = (TextView) getChildAt(i); if(checkList.get(i)){ view.setBackgroundDrawable(mContext.getDrawable(R.drawable.wall_flow_text_checked_background_layout)); }else{ view.setBackgroundDrawable(mContext.getDrawable(R.drawable.wall_flow_text_background_layout)); } } } } } /** * 重置TAG選中狀態 */ public void resetCheckList(){ for(int i = 0;i<checkList.size();i++){ checkList.set(i,false); } } /** * 獲取當前模式 * @return */ public int getNowMode(){ return mNowMode; } /** * 設置當前模式 * @param mode */ public void setNowMode(int mode){ mNowMode = mode; } }
這個還是個比較粗略的,只實現了單擊、單選、多選和動態添加。刪除沒有做,不過要做也不麻煩。