Android 自定義View - 助記詞
阿新 • • 發佈:2018-11-03
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>
對於這麼簡單的需求來說這個程式碼量稍微有點多~
唉寫完了自己也看不懂了,裡面有很多可以優化的地方,等有時間再來改改吧。