Android 自定义View - 助记词

UI今天给了个需求,把乱序的单词拼成一句话,如下图:
在这里插入图片描述
在这里插入图片描述
本来计划是用RecyclerView写的,突然脑子抽了想用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>

在这里插入图片描述

对于这么简单的需求来说这个代码量稍微有点多~
唉写完了自己也看不懂了,里面有很多可以优化的地方,等有时间再来改改吧。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值