編輯:關於Android編程
首先貼出實現的效果圖:
gif的效果可能有點過快,在真機上運行的效果會更好一些。我們主要的思路就是利用屬性動畫來動態地畫出選中狀態以及對勾的繪制過程。看到上面的效果圖,相信大家都迫不及待地要躍躍欲試了,那就讓我們開始吧。
自定義View的第一步:自定義屬性。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SmoothCheckBox"> <!-- 動畫持續時間 --> <attr name="duration" format="integer"></attr> <!-- 邊框寬度 --> <attr name="strikeWidth" format="dimension|reference"></attr> <!-- 邊框顏色 --> <attr name="borderColor" format="color|reference"></attr> <!-- 選中狀態的顏色 --> <attr name="trimColor" format="color|reference"></attr> <!-- 對勾顏色 --> <attr name="tickColor" format="color|reference"></attr> <!-- 對勾寬度 --> <attr name="tickWidth" format="dimension|reference"></attr> </declare-styleable> </resources>
我們把CheckBox取名為SmoothCheckBox,定義了幾個等等要用到的屬性。這一步很簡單,相信大家都熟練了。
接下來看一看onMeasure(int widthMeasureSpec, int heightMeasureSpec):
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize; } else { mWidth = 40; } int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; } else { mHeight = 40; } setMeasuredDimension(mWidth, mHeight); int size = Math.min(mWidth, mHeight); center = size / 2; mRadius = (int) ((size - mStrokeWidth) / 2 / 1.2f); startPoint.set(center * 14 / 30, center * 28 / 30); breakPoint.set(center * 26 / 30, center * 40 / 30); endPoint.set(center * 44 / 30, center * 20 / 30); downLength = (float) Math.sqrt(Math.pow(startPoint.x - breakPoint.x, 2f) + Math.pow(startPoint.y - breakPoint.y, 2f)); upLength = (float) Math.sqrt(Math.pow(endPoint.x - breakPoint.x, 2f) + Math.pow(endPoint.y - breakPoint.y, 2f)); totalLength = downLength + upLength; }
一開始是測量了SmoothCheckBox的寬、高度,默認的寬高度隨便定義了一個,當然你們可以自己去修改和完善它。然後就是設置半徑之類的,最後的startPoint、breakPoint、endPoint分別對應著選中時對勾的三個點(至於為何是這幾個數字,那完全是經驗值);downLength就是startPoint和breakPoint的距離,而相對應的upLength就是breakPoint和endPoint的距離。即以下圖示:
在看onDraw(Canvas canvas)之前我們先來看兩組動畫,分別是選中狀態時的動畫以及未選中狀態的動畫:
// 由未選中到選中的動畫 private void checkedAnimation() { animatedValue = 0f; tickValue = 0f; // 選中時底色的動畫 mValueAnimator = ValueAnimator.ofFloat(0f, 1.2f, 1f).setDuration(2 * duration / 5); mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); // 對勾的動畫 mTickValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(3 * duration / 5); mTickValueAnimator.setInterpolator(new LinearInterpolator()); mTickValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { // 得到動畫執行進度 tickValue = (float) valueAnimator.getAnimatedValue(); postInvalidate(); } }); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { // 得到動畫執行進度 animatedValue = (float) valueAnimator.getAnimatedValue(); postInvalidate(); } }); mValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { //當底色的動畫完成後再開始對勾的動畫 mTickValueAnimator.start(); Log.i(TAG," mTickValueAnimator.start();"); } }); mValueAnimator.start(); } // 由選中到未選中的動畫 private void uncheckedAnimation() { animatedValue = 0f; mValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(2 * duration / 5); mValueAnimator.setInterpolator(new AccelerateInterpolator()); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animatedValue = (float) valueAnimator.getAnimatedValue(); postInvalidate(); } }); mValueAnimator.start(); }
這兩組動畫在點擊SmoothCheckBox的時候會調用。相似的,都是在動畫執行中得到動畫執行的進度,再來調用postInvalidate();讓SmoothCheckBox重繪。看完這個之後就是終極大招onDraw(Canvas canvas)了:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); drawBorder(canvas); drawTrim(canvas); if (isChecked) { drawTick(canvas); } canvas.restore(); } // 畫對勾 private void drawTick(Canvas canvas) { // 得到畫對勾的進度 float temp = tickValue * totalLength; Log.i(TAG, "temp:" + temp + "downlength :" + downLength); //判斷是否是剛開始畫對勾的時候,即等於startPoint if (Float.compare(tickValue, 0f) == 0) { Log.i(TAG, "startPoint : " + startPoint.x + ", " + startPoint.y); path.reset(); path.moveTo(startPoint.x, startPoint.y); } // 如果畫對勾的進度已經超過breakPoint的時候,即(breakPoint,endPoint] if (temp > downLength) { path.moveTo(startPoint.x, startPoint.y); path.lineTo(breakPoint.x, breakPoint.y); Log.i(TAG, "endPoint : " + endPoint.x + ", " + endPoint.y); path.lineTo((endPoint.x - breakPoint.x) * (temp - downLength) / upLength + breakPoint.x, (endPoint.y - breakPoint.y) * (temp - downLength) / upLength + breakPoint.y); } else { //畫對勾的進度介於startPoinit和breakPoint之間,即(startPoint,breakPoint] Log.i(TAG, "down x : " + (breakPoint.x - startPoint.x) * temp / downLength + ",down y: " + (breakPoint.y - startPoint.y) * temp / downLength); path.lineTo((breakPoint.x - startPoint.x) * temp / downLength + startPoint.x, (breakPoint.y - startPoint.y) * temp / downLength + startPoint.y); } canvas.drawPath(path, tickPaint); } // 畫邊框 private void drawBorder(Canvas canvas) { float temp; // 通過animatedValue讓邊框產生一個“OverShooting”的動畫 if (animatedValue > 1f) { temp = animatedValue * mRadius; } else { temp = mRadius; } canvas.drawCircle(center, center, temp, borderPaint); } // 畫checkbox內部 private void drawTrim(Canvas canvas) { canvas.drawCircle(center, center, (mRadius - mStrokeWidth) * animatedValue, trimPaint); }
onDraw(Canvas canvas)代碼中的邏輯基本都加了注釋,主要就是原理搞懂了就比較簡單了。在繪制對勾時要區分當前處於繪制對勾的哪種狀態,然後對應做處理畫出線條,剩下的就簡單了。關於SmoothCheckBox的講解到這裡就差不多了。
下面就貼出SmoothCheckBox的完整代碼:
public class SmoothCheckBox extends View implements View.OnClickListener { // 動畫持續時間 private long duration; // 邊框寬度 private float mStrokeWidth; // 對勾寬度 private float mTickWidth; // 內飾畫筆 private Paint trimPaint; // 邊框畫筆 private Paint borderPaint; // 對勾畫筆 private Paint tickPaint; // 默認邊框寬度 private float defaultStrikeWidth; // 默認對勾寬度 private float defaultTickWidth; // 寬度 private int mWidth; // 高度 private int mHeight; // 邊框顏色 private int borderColor; // 內飾顏色 private int trimColor; // 對勾顏色 private int tickColor; // 半徑 private int mRadius; // 中心點 private int center; // 是否是選中 private boolean isChecked; //對勾向下的長度 private float downLength; //對勾向上的長度 private float upLength; // 對勾的總長度 private float totalLength; // 監聽器 private OnCheckedChangeListener listener; private ValueAnimator mValueAnimator; private ValueAnimator mTickValueAnimator; private float animatedValue; private float tickValue; // 對勾開始點 private Point startPoint = new Point(); // 對勾轉折點 private Point breakPoint = new Point(); // 對勾結束點 private Point endPoint = new Point(); private static final String TAG = "SmoothCheckBox"; private static final String KEY_INSTANCE_STATE = "InstanceState"; private Path path = new Path(); public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { this.listener = listener; } public SmoothCheckBox(Context context) { this(context, null); } public SmoothCheckBox(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SmoothCheckBox(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox); duration = a.getInt(R.styleable.SmoothCheckBox_duration, 600); defaultStrikeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()); mStrokeWidth = a.getDimension(R.styleable.SmoothCheckBox_strikeWidth, defaultStrikeWidth); defaultTickWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()); mTickWidth = a.getDimension(R.styleable.SmoothCheckBox_tickWidth, defaultTickWidth); borderColor = a.getColor(R.styleable.SmoothCheckBox_borderColor, getResources().getColor(android.R.color.darker_gray)); trimColor = a.getColor(R.styleable.SmoothCheckBox_trimColor, getResources().getColor(android.R.color.holo_green_light)); tickColor = a.getColor(R.styleable.SmoothCheckBox_tickColor, getResources().getColor(android.R.color.white)); a.recycle(); trimPaint = new Paint(Paint.ANTI_ALIAS_FLAG); trimPaint.setStyle(Paint.Style.FILL); trimPaint.setColor(trimColor); borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); borderPaint.setStrokeWidth(mStrokeWidth); borderPaint.setColor(borderColor); borderPaint.setStyle(Paint.Style.STROKE); tickPaint = new Paint(Paint.ANTI_ALIAS_FLAG); tickPaint.setColor(tickColor); tickPaint.setStyle(Paint.Style.STROKE); tickPaint.setStrokeCap(Paint.Cap.ROUND); tickPaint.setStrokeWidth(mTickWidth); setOnClickListener(this); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize; } else { mWidth = 40; } int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; } else { mHeight = 40; } setMeasuredDimension(mWidth, mHeight); int size = Math.min(mWidth, mHeight); center = size / 2; mRadius = (int) ((size - mStrokeWidth) / 2 / 1.2f); startPoint.set(center * 14 / 30, center * 28 / 30); breakPoint.set(center * 26 / 30, center * 40 / 30); endPoint.set(center * 44 / 30, center * 20 / 30); downLength = (float) Math.sqrt(Math.pow(startPoint.x - breakPoint.x, 2f) + Math.pow(startPoint.y - breakPoint.y, 2f)); upLength = (float) Math.sqrt(Math.pow(endPoint.x - breakPoint.x, 2f) + Math.pow(endPoint.y - breakPoint.y, 2f)); totalLength = downLength + upLength; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); drawBorder(canvas); drawTrim(canvas); if (isChecked) { drawTick(canvas); } canvas.restore(); } @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState()); bundle.putBoolean(KEY_INSTANCE_STATE, isChecked); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; boolean isChecked = bundle.getBoolean(KEY_INSTANCE_STATE); setChecked(isChecked); super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE)); return; } super.onRestoreInstanceState(state); } // 切換狀態 private void toggle() { isChecked = !isChecked; if (listener != null) { listener.onCheckedChanged(this, isChecked); } if (isChecked) { checkedAnimation(); } else { uncheckedAnimation(); } } // 由未選中到選中的動畫 private void checkedAnimation() { animatedValue = 0f; tickValue = 0f; mValueAnimator = ValueAnimator.ofFloat(0f, 1.2f, 1f).setDuration(2 * duration / 5); mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); mTickValueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(3 * duration / 5); mTickValueAnimator.setInterpolator(new LinearInterpolator()); mTickValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { tickValue = (float) valueAnimator.getAnimatedValue(); postInvalidate(); } }); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animatedValue = (float) valueAnimator.getAnimatedValue(); postInvalidate(); } }); mValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mTickValueAnimator.start(); Log.i(TAG," mTickValueAnimator.start();"); } }); mValueAnimator.start(); } // 由選中到未選中的動畫 private void uncheckedAnimation() { animatedValue = 0f; mValueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(2 * duration / 5); mValueAnimator.setInterpolator(new AccelerateInterpolator()); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animatedValue = (float) valueAnimator.getAnimatedValue(); postInvalidate(); } }); mValueAnimator.start(); } // 畫對勾 private void drawTick(Canvas canvas) { float temp = tickValue * totalLength; Log.i(TAG, "temp:" + temp + "downlength :" + downLength); if (Float.compare(tickValue, 0f) == 0) { Log.i(TAG, "startPoint : " + startPoint.x + ", " + startPoint.y); path.reset(); path.moveTo(startPoint.x, startPoint.y); } if (temp > downLength) { path.moveTo(startPoint.x, startPoint.y); path.lineTo(breakPoint.x, breakPoint.y); Log.i(TAG, "endPoint : " + endPoint.x + ", " + endPoint.y); path.lineTo((endPoint.x - breakPoint.x) * (temp - downLength) / upLength + breakPoint.x, (endPoint.y - breakPoint.y) * (temp - downLength) / upLength + breakPoint.y); } else { Log.i(TAG, "down x : " + (breakPoint.x - startPoint.x) * temp / downLength + ",down y: " + (breakPoint.y - startPoint.y) * temp / downLength); path.lineTo((breakPoint.x - startPoint.x) * temp / downLength + startPoint.x, (breakPoint.y - startPoint.y) * temp / downLength + startPoint.y); } canvas.drawPath(path, tickPaint); } // 畫邊框 private void drawBorder(Canvas canvas) { float temp; if (animatedValue > 1f) { temp = animatedValue * mRadius; } else { temp = mRadius; } canvas.drawCircle(center, center, temp, borderPaint); } // 畫checkbox內部 private void drawTrim(Canvas canvas) { canvas.drawCircle(center, center, (mRadius - mStrokeWidth) * animatedValue, trimPaint); } @Override public void onClick(View view) { toggle(); } /** * 判斷checkbox是否選中狀態 * * @return */ public boolean isChecked() { return isChecked; } /** * 設置checkbox的狀態 * * @param isChecked 是否選中 */ public void setChecked(boolean isChecked) { this.setChecked(isChecked, false); } /** * 設置checkbox的狀態 * * @param isChecked 是否選中 * @param isAnimation 切換時是否有動畫 */ public void setChecked(boolean isChecked, boolean isAnimation) { this.isChecked = isChecked; if (isAnimation) { if (isChecked) { checkedAnimation(); } else { uncheckedAnimation(); } } else { animatedValue = isChecked ? 1f : 0f; tickValue = 1f; invalidate(); } if (listener != null) { listener.onCheckedChanged(this, isChecked); } } public interface OnCheckedChangeListener { void onCheckedChanged(SmoothCheckBox smoothCheckBox, boolean isChecked); } }
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
在開發Android應用時,保存數據有這麼幾個方式, 一個是本地保存,一個是放在後台(提供API接口),還有一個是放在開放雲服務上(如 SyncAdapter 會是一個不
13.如何全編譯代碼?由於上面介紹了如何連接真機進行調試,因此必須趕緊補充上全編譯的方法。因為要進行聯機調試,之前首先得將對應的代碼進行全編譯。很多新人在進行聯機調試的時
前言在學會使用Maven創建Java程序之後。我們試著去用Maven創建Android程序還是先用Maven命令在完成,這樣我們可以清楚,編譯器為我們做了些什麼基於Mav
前言有關Android進程間通信之Aidl編程的基本使用步驟已經在上一篇博客中有講解,Android studio 下的aidl編程實現Android的誇進程間通信。上一
compile 'com.android.suppo