自定義view之流式佈局

前言

網上關於流式佈局的代碼已經很多了,但是我爲什麼又要去寫呢,主要是在實際使用中發現定製化困難的緣故了.另一方面,湊文章數 😃
目前支持新增,長按刪除,點擊item切換背景和字體顏色,以及item的單擊和長按回調.

思路

一個文字列表,逐行排列,在每次開始的時候計算一下同一行剩餘的width是否足夠文字顯示,如果不夠,就增加高度,即移至下一行.
先看一下效果:
Alt Text

不過目前有一處並未做處理,就是view整體的高度自動適配,這個等我先把DailyLife做完之後去優化吧.
代碼參考如下:

public class FlowLayout extends View {
    private static final String TAG = "FlowLayout";
    private Paint mPaint;
    private List<String> textList = new ArrayList<>();
    private int minPadding = 80;
    private int textPadding = 20;
    private float textSize = 30;
    private int textPace = 35;                                //兩個item的間隔
    private int textHeight = 40;
    private int distanceW, distanceH;
    private List<int[]> pointList = new ArrayList<>();
    private int clickIndex = -1;
    private long touchDownTime;
    private boolean isLongClick;
    private int textNormalColor;
    private int textClickColor;
    private boolean isShowEndAdd;
    private FlowClickListener listener;
    private float downY;
    private boolean paddingHasChanged;

    public FlowLayout(Context context) {
        this(context, null);
    }

    public FlowLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlowLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        textNormalColor = a.getColor(R.styleable.FlowLayout_flow_text_normal_color, Color.BLUE);
        textClickColor = a.getColor(R.styleable.FlowLayout_flow_text_click_color, Color.WHITE);
        textSize = a.getDimension(R.styleable.FlowLayout_flow_text_size, context.getResources().getDimension(R.dimen.dimen_size_15));
        initPaint();
    }

    public boolean isLongClick() {
        return isLongClick;
    }

    public void setData(List<String> textList) {
        setData(textList, false, -1);
    }

    public void setData(List<String> textList, boolean isShowEndAdd, int clickIndex) {
        this.isShowEndAdd = isShowEndAdd;
        this.clickIndex = clickIndex;
        this.textList.clear();
        this.textList.addAll(textList);
        postInvalidate();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setTextSize(textSize);
        Rect rect = new Rect();
        mPaint.getTextBounds("1", 0, "1".length(), rect);
        textHeight = rect.height();
        textPadding = textHeight;
        textPace = textHeight * 3 / 2;
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (textList.size() <= 0)
            return;
        int startTextTop = minPadding;
        int startTextLeft;
        int totalTextWidth = 0;
        pointList.clear();
        mPaint.setTextSize(textSize);
        for (int i = 0; i < textList.size(); i++) {
            mPaint.setTextAlign(Paint.Align.LEFT);
            float width = mPaint.measureText(textList.get(i));
            int lastWidth = totalTextWidth;
            totalTextWidth += (width + textPadding * 2 + textPace);
            if (totalTextWidth + textHeight * 2 > distanceW) {  //判斷width 是否夠
                totalTextWidth = (int) (width + textPadding * 2 + textPace);
                startTextLeft = textHeight * 2;
                startTextTop += textHeight + textPadding * 2 + textPace;
            } else {
                startTextLeft = lastWidth + textHeight * 2;
            }

            mPaint.setColor(textNormalColor);
            boolean isShowClickType = i == clickIndex && null != listener && (!isShowEndAdd || i != textList.size() - 1) && !paddingHasChanged;
            mPaint.setStyle(isShowClickType ? Paint.Style.FILL : Paint.Style.STROKE);
            int[] array = {startTextLeft - textPadding, startTextTop - textPadding - textHeight,
                    (int) (startTextLeft + width + textPadding), startTextTop + textPadding};
            canvas.drawRect(array[0], array[1], array[2], array[3], mPaint);
            mPaint.setColor(isShowClickType ? textClickColor : textNormalColor);
            canvas.drawText(textList.get(i), startTextLeft, startTextTop, mPaint);
            if (isLongClick && null != listener && (!isShowEndAdd || i != textList.size() - 1) && !paddingHasChanged) {
                mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
                mPaint.setColor(textClickColor);
                canvas.drawCircle(startTextLeft + width + textPadding, startTextTop - textPadding * 2, textPadding, mPaint);
                mPaint.setColor(textNormalColor);
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(startTextLeft + width + textPadding, startTextTop - textPadding * 2, textPadding, mPaint);
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText("x", startTextLeft + width + textPadding, startTextTop - textPadding * 2 + textHeight / 2, mPaint);
                int part = textPadding + 5;
                array[1] -= part;
                array[2] += part;
                array[0] = array[2] - part * 2;
                array[3] = array[1] + part * 2;
            }
            pointList.add(array);
        }
    }

    public void clearDeleteType() {
        clickIndex = -1;
        isLongClick = false;
        postInvalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int resultWidth = sizeWidth;
        int resultHeight = sizeHeight;
        // 考慮內邊距對尺寸的影響
        resultWidth += getPaddingLeft() + getPaddingRight();
        resultHeight += getPaddingTop() + getPaddingBottom();
        // 考慮父容器對尺寸的影響
        distanceW = resultWidth = resolveMeasure(sizeWidth, resultWidth);
        distanceH = resultHeight = resolveMeasure(sizeHeight, resultHeight);
        Log.d(TAG, "onMeasure: sizeHeight: " + sizeHeight + ",resultHeight: " + resultHeight);
        setMeasuredDimension(resultWidth, resultHeight);
    }

    /**
     * 根據傳入的值進行測量
     */
    public int resolveMeasure(int measureSpec, int defaultSize) {
        int result = 0;
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                //設置warp_content時設置默認值
                result = Math.min(specSize, defaultSize);
                break;
            default:
                result = defaultSize;
        }
        return result;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                if (paddingHasChanged) {
                    postInvalidate();
                    paddingHasChanged = false;
                }
                if (System.currentTimeMillis() - touchDownTime > 500) {
                    if (null != listener) {
                        clickIndex = -1;
                        isLongClick = !isLongClick;
                        postInvalidate();
                    }
                } else {
                    int i = 0;
                    float x = event.getX();
                    float y = event.getY();
                    for (int[] arr : pointList) {
                        if (x >= arr[0] && x <= arr[2] && y >= arr[1] && y <= arr[3]) {
                            clickIndex = i;
                            break;
                        }
                        i++;
                    }

                    if (null != listener && clickIndex >= 0) {
                        if (isLongClick)
                            listener.setOnClickLongItemListener(clickIndex);
                        else
                            listener.setOnClickItemListener(clickIndex);
                        postInvalidate();
                    }
                }
                break;

            case MotionEvent.ACTION_DOWN:
                downY = event.getY();
                paddingHasChanged = false;
                touchDownTime = System.currentTimeMillis();
                break;
        }
        return true;
    }

    public void removeItem(int index) {
        textList.remove(index);
        clickIndex = -1;
        postInvalidate();
    }

    public void setFlowClickListener(FlowClickListener clickListener) {
        this.listener = clickListener;
    }

    public interface FlowClickListener {
        void setOnClickItemListener(int index);

        void setOnClickLongItemListener(int index);
    }
}

attrs.xml文件內容

  <declare-styleable name="FlowLayout">
        <attr name="flow_text_normal_color" format="color"/>
        <attr name="flow_text_click_color" format="color"/>
        <attr name="flow_text_size" format="dimension"/>
    </declare-styleable>

最後

地址等我明天上傳吧,順便把寫過的自定義view統一做一下處理.

誒,小熊錄屏的gif爲什麼不能在csdn播放啊啊啊啊啊啊,明天找原因.

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