1. 程式人生 > >Android 自定義View - 助記詞

Android 自定義View - 助記詞

UI今天給了個需求,把亂序的單詞拼成一句話,如下圖:
在這裡插入圖片描述
在這裡插入圖片描述
本來計劃是用ViewGroup+TextView寫的,突然腦子抽了想用paint來畫(寫完就後悔了-.-)
先看一下完成後的效果
在這裡插入圖片描述

前期的思路是這樣的:
1.測量每個單詞的高度和寬度,計算出所有單詞排列後的高*2,就是view 的高度
2.把文字寫上去
在這裡插入圖片描述

在這裡插入圖片描述
哈哈,這個的確就是當時的想法,不要慫就是幹~
那就來試試吧,話不多說,直接上程式碼:

public class HelpWordView extends View {

    private Context mContext;

    private int rx = 10, ry = 10;
    private int mWidth;
    private int mHeight;
    private float mOffsetX;//X軸偏移量
    private float mOffsetY;//Y軸偏移量
    private int mScreenWidth;

    private int mTextColor;//字型顏色
    private int mPlaceColor;//放置區背景顏色
    private int mSelectColor;//選擇區背景顏色
    private float mTextSize;//字型大小
    private float mTextMarginLeft;//距離左邊間隔
    private float mTextMarginTop;//距離上面間隔

    private ArrayList<Word> mData;
    private ArrayList<Word> mShuffleData;
    private ArrayList<RectF> mSelectRectF;
    private ArrayList<RectF> mPlaceRectF;
    private ArrayList<String> mPlaceData;

    private Paint mTextPaint;
    private Paint mShapePaint;
    private Paint mPlacePaint;
    private Paint mSelectPaint;
    private Paint mTextMeasurePaint;

    private onResultCallback mOnResultCallback;

    private static final String TAG = "HelpWordView";


    public HelpWordView(Context context) {
        super(context);
    }

    public HelpWordView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HelpWordView);
        mTextSize = typedArray.getDimension(R.styleable.HelpWordView_text_size, 35f);
        mTextMarginLeft = typedArray.getDimension(R.styleable.HelpWordView_text_marginLeft, 50f);
        mTextMarginTop = typedArray.getDimension(R.styleable.HelpWordView_text_marginTop, 30f);
        mTextColor = typedArray.getColor(R.styleable.HelpWordView_text_color, Color.BLACK);
        mPlaceColor = typedArray.getColor(R.styleable.HelpWordView_place_background_color, Color.rgb(240, 240, 240));
        mSelectColor = typedArray.getColor(R.styleable.HelpWordView_select_background_color, Color.WHITE);
        typedArray.recycle();//回收
        init();
    }

    public ArrayList<Word> getData() {
        return mData;
    }

    public void setData(ArrayList<Word> mData) {
        this.mData = mData;
        this.mShuffleData = new ArrayList<>();
        this.mShuffleData.addAll(mData);
        Collections.shuffle(mShuffleData);
        invalidate();
    }

    public void setOnResultCallback(onResultCallback mOnResultCallback) {
        this.mOnResultCallback = mOnResultCallback;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        float tempWidth = 0;//儲存寬度的臨時變數
        float totalHeight = getTextHeight(mTextMeasurePaint) * 3;//view所佔的螢幕高度
        if (null != mShuffleData) {
            for (Word word : mShuffleData) {
                tempWidth += getTextWidth(word.getWord(), mTextMeasurePaint) + mTextMarginLeft;//累加計算出當前文字的寬度
                if (tempWidth >= mScreenWidth) {//如果累加的寬度大於螢幕的寬度則換行
                    totalHeight += getTextHeight(mTextMeasurePaint) + mTextMarginTop;
                    tempWidth = getTextWidth(word.getWord(), mTextMeasurePaint) + mTextMarginLeft;
                }
            }
        }
        totalHeight += totalHeight;
        setMeasuredDimension(mScreenWidth, (int) totalHeight);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawPlace(canvas);
        drawSelect(canvas);
    }

    //畫放置區的文字
    private void drawPlace(Canvas canvas) {
        mPlaceRectF = new ArrayList<>();
        RectF rectF = new RectF(0, 0, mWidth, mHeight / 2);
        canvas.drawRect(rectF, mPlacePaint);

        float startX = 0;
        float startY = getTextHeight(mTextMeasurePaint);
        float tempX = 0;

        for (String str : mPlaceData) {

            if (tempX + getTextWidth(str, mTextMeasurePaint) + mTextMarginLeft + mOffsetX >= mScreenWidth) {
                tempX = 0;
                startX = 0;
                startY += getTextHeight(mTextMeasurePaint) + mTextMarginTop;
            }

            tempX += getTextWidth(str, mTextMeasurePaint) + mTextMarginLeft;
            RectF position = new RectF(startX, startY - getTextHeight(mTextMeasurePaint), tempX - mTextMarginLeft, startY);
            mPlaceRectF.add(position);

            if (tempX >= mScreenWidth) {
                tempX = 0;
                startY += getTextHeight(mTextMeasurePaint) + mTextMarginTop;
            }
            startX = tempX;
        }

        ArrayList<ArrayList<RectF>> total = new ArrayList<>();
        ArrayList<RectF> temp = new ArrayList<>();
        for (int i = 0; i < mPlaceRectF.size(); i++) {
            if (i > 0) {
                if (mPlaceRectF.get(i).bottom == mPlaceRectF.get(i - 1).bottom) {
                    temp.add(mPlaceRectF.get(i));
                } else {
                    total.add(temp);
                    temp = new ArrayList<>();
                    temp.add(mPlaceRectF.get(i));
                }
            } else {
                temp.add(mPlaceRectF.get(i));
            }
        }
        if (temp.size() > 0) {
            total.add(temp);
        }

        if (total.size() > 0) {
            mPlaceRectF = new ArrayList<>();

            for (int i = 0; i < total.size(); i++) {
                for (RectF f : total.get(i)) {
                    f.offset(mOffsetX, mOffsetY);
                    mPlaceRectF.add(f);
                }
            }
        }

        for (int i = 0; i < mPlaceRectF.size(); i++) {
            float textWidth = getTextWidth(mPlaceData.get(i), mTextPaint);
            float textHeight = getTextHeight(mTextPaint);
            float textX = (float) (mPlaceRectF.get(i).left + textWidth);
            float textY = (float) (mPlaceRectF.get(i).bottom - textHeight / 2.0);
            canvas.drawRoundRect(mPlaceRectF.get(i), rx, ry, mSelectPaint);
            canvas.drawText(mPlaceData.get(i), textX, textY, mTextPaint);
            canvas.drawRoundRect(mPlaceRectF.get(i), rx, ry, mShapePaint);
        }

    }

    //畫選擇區的文字
    private void drawSelect(Canvas canvas) {
        mSelectRectF = new ArrayList<>();
        //畫選擇區的矩形
        RectF rectF = new RectF(0, mHeight / 2, mWidth, mHeight);
        canvas.drawRect(rectF, mSelectPaint);

        float startX = 0;
        float startY = getTextHeight(mTextMeasurePaint) + mHeight / 2;
        float tempX = 0;
        for (Word word : mShuffleData) {
            if (tempX + getTextWidth(word.getWord(), mTextMeasurePaint) + mTextMarginLeft >= mScreenWidth) {
                tempX = 0;
                startX = 0;
                startY += getTextHeight(mTextMeasurePaint) + mTextMarginTop;
            }

            tempX += getTextWidth(word.getWord(), mTextMeasurePaint) + mTextMarginLeft;

            RectF position = new RectF(startX, startY - getTextHeight(mTextMeasurePaint), tempX - mTextMarginLeft, startY);
            mSelectRectF.add(position);

            if (tempX >= mScreenWidth) {
                tempX = 0;
                startY += getTextHeight(mTextMeasurePaint) + mTextMarginTop;
            }
            startX = tempX;
        }

        ArrayList<ArrayList<RectF>> total = new ArrayList<>();
        ArrayList<RectF> temp = new ArrayList<>();
        for (int i = 0; i < mSelectRectF.size(); i++) {
            if (i > 0) {
                if (mSelectRectF.get(i).bottom == mSelectRectF.get(i - 1).bottom) {
                    temp.add(mSelectRectF.get(i));
                } else {
                    total.add(temp);
                    temp = new ArrayList<>();
                    temp.add(mSelectRectF.get(i));
                }
            } else {
                temp.add(mSelectRectF.get(i));
            }
        }

        if (temp.size() > 0) {
            total.add(temp);
        }

        if (total.size() > 0) {
            mSelectRectF = new ArrayList<>();
            float lastTop = total.get(total.size() - 1).get(total.get(total.size() - 1).size() - 1).top;
            float lastBottom = total.get(total.size() - 1).get(total.get(total.size() - 1).size() - 1).bottom;
            float freeY = mHeight - lastTop - (lastBottom - lastTop) / 2;

            int maxIndex = isBest(total);

            Log.e(TAG, "drawSelect: maxIndex = " + maxIndex);

            float freeX = mWidth - total.get(maxIndex).get(total.get(maxIndex).size() - 1).right;

            mOffsetX = (float) (freeX / (total.get(maxIndex).size() - 1.0));
            mOffsetY = (float) (freeY / (total.size() + 1 + 0.0));

            if (total.size() == 1) {
                mOffsetY = freeY / 3;
            }
            for (int i = 0; i < total.size(); i++) {

                for (RectF f : total.get(i)) {
                    f.offset(mOffsetX, mOffsetY);
                    mSelectRectF.add(f);
                }
            }
        }

        for (int i = 0; i < mSelectRectF.size(); i++) {
            if (!mShuffleData.get(i).isHide()) {
                float textWidth = getTextWidth(mShuffleData.get(i).getWord(), mTextPaint);
                float textHeight = getTextHeight(mTextPaint);
                float textX = (float) (mSelectRectF.get(i).left + textWidth);
                float textY = (float) (mSelectRectF.get(i).bottom - textHeight / 2.0);
                canvas.drawText(mShuffleData.get(i).getWord(), textX, textY, mTextPaint);
            }
            canvas.drawRoundRect(mSelectRectF.get(i), rx, ry, mShapePaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (int i = 0; i < mSelectRectF.size(); i++) {
                    if (mSelectRectF.get(i).contains(event.getX(), event.getY())) {
                        if (!mShuffleData.get(i).isHide()) {
                            mPlaceData.add(mShuffleData.get(i).getWord());
                            mShuffleData.get(i).setHide(!mShuffleData.get(i).isHide());
                        }
                        invalidate();
                        break;
                    }
                }

                for (int i = 0; i < mPlaceRectF.size(); i++) {
                    if (mPlaceRectF.get(i).contains(event.getX(), event.getY())) {
                        setShow(mPlaceData.get(i));
                        mPlaceRectF.remove(i);
                        mPlaceData.remove(i);
                        invalidate();
                        break;
                    }
                }
                compare();
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    private void init() {

        mScreenWidth = getScreenWidth();

        if (null == mTextPaint) {
            mTextPaint = new Paint();
            mTextPaint.setTextSize(mTextSize);
            mTextPaint.setAntiAlias(true);
            mTextPaint.setColor(mTextColor);
            mTextPaint.setTextAlign(Paint.Align.CENTER);
        }

        if (null == mTextMeasurePaint) {
            mTextMeasurePaint = new Paint();
            mTextMeasurePaint.setAntiAlias(true);
            mTextMeasurePaint.setTextSize((float) (mTextSize * 2));
            mTextMeasurePaint.setColor(mTextColor);
        }

        if (null == mPlacePaint) {
            mPlacePaint = new Paint();
            mPlacePaint.setColor(mPlaceColor);
            mPlacePaint.setAntiAlias(true);
            mPlacePaint.setStyle(Paint.Style.FILL);
        }

        if (null == mSelectPaint) {
            mSelectPaint = new Paint();
            mSelectPaint.setAntiAlias(true);
            mSelectPaint.setColor(mSelectColor);
            mSelectPaint.setStyle(Paint.Style.FILL);
        }

        if (null == mShapePaint) {
            mShapePaint = new Paint();
            mShapePaint.setAntiAlias(true);
            mShapePaint.setStyle(Paint.Style.STROKE);
            mShapePaint.setStrokeWidth(2);
            mShapePaint.setColor(Color.GRAY);
        }

        if (null == mSelectRectF) {
            mSelectRectF = new ArrayList<>();
        }

        if (null == mPlaceRectF) {
            mPlaceRectF = new ArrayList<>();
        }

        if (null == mPlaceData) {
            mPlaceData = new ArrayList<>();
        }
    }

    //比較使用者選擇的字串順序
    private void compare() {
        if (null == mData) {
            return;
        }
        if (null == mPlaceData) {
            return;
        }
        if (null != mOnResultCallback) {
            int count = 0;
            for (int i = 0; i < mPlaceData.size(); i++) {
                if (!mPlaceData.get(i).equals(mData.get(i).getWord())) {
                    count++;
                }
            }
            mOnResultCallback.error(count);
            if (mPlaceData.size() == mData.size()) {
                if (count == 0) {
                    mOnResultCallback.right();
                }
            }
        }
    }

    private int isBest(ArrayList<ArrayList<RectF>> lists) {

        ArrayList<Right> sortList = new ArrayList<>();

        for (int i = 0; i < lists.size(); i++) {
            sortList.add(new Right(i, lists.get(i).get(lists.get(i).size() - 1).right));
        }

        Collections.sort(sortList, new Comparator<Right>() {

            /**
             * 返回負數表示:o1 小於o2,
             * 返回0 表示:o1和o2相等,
             * 返回正數表示:o1大於o2*/
            @Override
            public int compare(Right o1, Right o2) {
                if (o1.value >= o2.value) {
                    return 1;
                }
                return -1;
            }
        });

        for (int i = sortList.size() - 1; i >= 0; i--) {
            ArrayList<RectF> rectFS = lists.get(sortList.get(i).index);
            if ((mWidth - rectFS.get(rectFS.size() - 1).right) / (rectFS.size() - 1.0) + rectFS.get(rectFS.size() - 1).right > mWidth) {
                continue;
            }
            return sortList.get(i).index;
        }
        return 0;
    }

    //顯示字串
    private void setShow(String text) {
        for (Word word : mShuffleData) {
            if (word.getWord().equals(text)) {
                word.setHide(!word.isHide());
            }
        }
    }

    //獲取文字的高
    private int getTextHeight(Paint paint) {

        Rect rect = new Rect();
        paint.getTextBounds("測試", 0, "測試".length(), rect);
        return rect.height();
    }

    //獲取文字的寬
    private float getTextWidth(String text, Paint paint) {
        return paint.measureText(text);
    }

    //獲取螢幕的寬度
    private int getScreenWidth() {
        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }


    public interface onResultCallback {

        void right();

        void error(int count);
    }

    private class Right {
        int index;
        float value;

        public Right(int index, float value) {
            this.index = index;
            this.value = value;
        }
    }

    public static class Word {

        private String word;
        private boolean hide;

        public Word(String word) {
            this.word = word;
        }

        public Word(String word, boolean hide) {
            this.word = word;
            this.hide = hide;
        }

        public String getWord() {
            return word;
        }

        public void setWord(String word) {
            this.word = word;
        }

        public boolean isHide() {
            return hide;
        }

        public void setHide(boolean hide) {
            this.hide = hide;
        }
    }
}

在values新建一個attrs.xml檔案

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="HelpWordView">

        <!--放置區的背景顏色-->
        <attr name="place_background_color" format="color" />
        <!--選擇區的背景顏色-->
        <attr name="select_background_color" format="color" />
        <!--文字大小-->
        <attr name="text_size" format="dimension" />
        <!--距離左邊間隔-->
        <attr name="text_marginLeft" format="dimension" />
        <!--距離上面間隔-->
        <attr name="text_marginTop" format="dimension" />
        <!--文字顏色-->
        <attr name="text_color" format="color" />

    </declare-styleable>

</resources>

在這裡插入圖片描述

對於這麼簡單的需求來說這個程式碼量稍微有點多~
唉寫完了自己也看不懂了,裡面有很多可以優化的地方,等有時間再來改改吧。