android自定義view 流式標籤View

前段時間,項目裏要做一個熱搜詞的頁面,當時隨便在網上找了一個改了改就拿來用了。這幾天稍微有點空閒,想辦法再來改善改善。

這是改完後的效果:
























一個個標籤都是單獨的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;
    }

}


這個還是個比較粗略的,只實現了單擊、單選、多選和動態添加。刪除沒有做,不過要做也不麻煩。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章