Android自訂View製作動態炫酷按鈕執行個體解析_Android

來源:互聯網
上載者:User

普通按鈕也就那麼幾種樣式,看著都審美疲勞,先放效果圖:

 

你會不會以為這個按鈕是集結了很多動畫的產物,我告訴你,並沒有。所有的實現都是基於自訂View,採用最底層的onDraw一點一點的畫出來的。沒有採用一丁點的動畫。雖然示範時間很短,但是要完成這麼多變化,還是挺吃力。
首先講解用法: 

public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final AnimationButton button = (AnimationButton) findViewById(R.id.button);// button.setTextSizeTouch(25); //設定按下時字型的大小,不設定有預設值// button.setStrokeProgress(10); //設定進度條的厚度,不設定有預設值// button.setColorBase(Color.GREEN); //設定整體基色,不設定有預設值// button.setColorBack(Color.GRAY); //設定進度條的背景色,不設定有預設值// button.setStroke(3); //設定邊框的厚度,不設定有預設值// button.setStrokeText(0); //設定文本的厚度,不設定有預設值// button.setTextSize(30); //設定文本的字型大小,不設定有預設值// button.setRound(30); //設定圓角,不設定有預設值 button.setText("登入"); //設定文本,不設定有預設值 button.setMode(AnimationButton.Mode.Hand_Finish); //設定進度條模式,不設定有預設值Mode.Auto_Finish button.setOnAnimationButtonClickListener(new AnimationButton.OnAnimationButtonClickListener() {  @Override  public void onClick() {  //stopProgress方法 僅僅在button.setMode(AnimationButton.Mode.Hand_Finish);之後才有效。  button.stopProgress();  } }); }} 

其實如果只需要最普通的功能,根本什麼都不用做。因為幾乎所有的參數都已經設定了固定內設定。在上面注釋掉的函數用法也是使用者唯一能用的幾個函數,其他函數雖然標示為public,但是卻是因為組件之內的方法傳遞,而不是給外界使用者調用的。因此大家如果想自訂樣式,可以調用注釋裡的方法。  

下面開始源碼講解,首先分解功能,所有的變化可以分為三個狀態: 
1、預設狀態,也就是最初的狀態。主要完成的事情為:接收使用者的點擊,改變背景的樣式從空心變為實心,動態改變文本的大小,然後就是逐漸得縮小成一個圓。 
2、進度條狀態。主要完成進度條的遞進,示範圖上只轉了一圈。其實可以通過設定一個參數,轉動多圈直到使用者手動停止,甚至無限轉動 
3、結束狀態。主要完成由圓的狀態變回圓角矩形的狀態,並呈現中間的Logo  

既然分割出了狀態,那麼就採用狀態機器+代理模式來實現這個功能吧。首先是狀態的枚舉。 

/** * Created by ccwxf on 2016/2/29. * 用於區別狀態,有:預設狀態、進度條狀態、結束狀態 */public enum Status { Default, Progress, Finish}

 然後是狀態機器的介面,也就是所有的狀態需要完成的共同的事情:

/** * Created by ccwxf on 2016/2/29. */public interface ButtonStatus { /** * @return 對應的Status值 */ Status getStatus(); /** * 這個狀態的事件處理代理 * @param mEvent * @return */ boolean onTouchEvent(MotionEvent mEvent); /** * 這個狀態的繪製代理 * @param mCanvas * @param mPaint */ void onDraw(Canvas mCanvas, Paint mPaint);}

 然後我們實現按鈕的代碼,也就是自訂View: 

/** * Created by ccwxf on 2016/2/29. */public class AnimationButton extends View { private static int Color_Base = Color.rgb(24, 204, 149); private static int Color_Back = Color.rgb(153, 153, 153); private static int Stroke = 3; private static int Stroke_Text = 0; private static int Stroke_Progress = 10; private static int Text_Size = 30; private static int Text_Size_Touch = 25; private static int Round = 30; private static String Text = "提交"; private Mode mode = Mode.Auto_Finish; private int maxWidth; private int maxHeight; private int colorBase = Color_Base; private int colorBack = Color_Back; private int stroke = Stroke; private int strokeText = Stroke_Text; private int strokeProgress = Stroke_Progress; private int textSize = Text_Size; private int textSizeTouch = Text_Size_Touch; private int round = Round; private String text = Text; //是否停止進度條,由外界設定 private boolean isProgressStop = false; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private ButtonStatus status; private OnAnimationButtonClickListener listener; public Mode getMode() { return mode; } public void setMode(Mode mode) { this.mode = mode; } public int getMaxWidth() { return maxWidth; } public int getMaxHeight() { return maxHeight; } public int getTextSizeTouch() { return textSizeTouch; } public void setTextSizeTouch(int textSizeTouch) { this.textSizeTouch = textSizeTouch; } public int getStrokeProgress() { return strokeProgress; } public void setStrokeProgress(int strokeProgress) { this.strokeProgress = strokeProgress; } public int getColorBase() { return colorBase; } public void setColorBase(int colorBase) { this.colorBase = colorBase; } public int getColorBack() { return colorBack; } public void setColorBack(int colorBack) { this.colorBack = colorBack; } public int getStroke() { return stroke; } public void setStroke(int stroke) { this.stroke = stroke; } public int getStrokeText() { return strokeText; } public void setStrokeText(int strokeText) { this.strokeText = strokeText; } public int getTextSize() { return textSize; } public void setTextSize(int textSize) { this.textSize = textSize; } public int getRound() { return round; } public void setRound(int round) { this.round = round; } public String getText() { return text; } public void setText(String text) { this.text = text; } public AnimationButton(Context context) { super(context); } public AnimationButton(Context context, AttributeSet attrs) { super(context, attrs); } public AnimationButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent event) { if (status != null) {  return status.onTouchEvent(event); } return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { if (status != null) {  status.onDraw(canvas, mPaint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { maxWidth = MeasureSpec.getSize(widthMeasureSpec); maxHeight = MeasureSpec.getSize(heightMeasureSpec); if (maxWidth != 0 && maxHeight != 0) {  status = new DefaultStatus(this, maxWidth, maxHeight); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 改變整體狀態 * * @param s 改變的狀態 * @param width 目前的寬度 * @param height 目前的高度 */ public void changeStatus(Status s, int width, int height, int centerX, int centerY) { switch (s) {  case Default:  break;  case Progress:  //改變狀態,進入進度條狀態  status = new ProgressStatus(this, width, height, centerX, centerY);  invalidate();  break;  case Finish:  //進入結束狀態  status = new FinishStatus(this, width, height, centerX, centerY);  invalidate();  break; } } /** * 外界設定停止進度條 */ public void stopProgress(){ this.isProgressStop = true; } /** * 檢查是否進度條結束 * @return */ public boolean isProgressStop(){ return isProgressStop; } public enum Mode{ Auto_Finish, Hand_Finish } public interface OnAnimationButtonClickListener{ void onClick(); } public void setOnAnimationButtonClickListener(OnAnimationButtonClickListener listener){ this.listener = listener; } public OnAnimationButtonClickListener getOnAnimationButtonClickListener(){ return listener; }}
 

上面實現了一堆的變數參數供使用者自訂。然後在onTouchEvent和onDraw方法中,將所有操作都代理出去。 
然後我們來實現第一個狀態,也就是預設狀態: 

/** * Created by ccwxf on 2016/2/29. */public class DefaultStatus implements ButtonStatus { //分別表示處於預設狀態內部的四個子狀態 private static final int Status_Default = 0; private static final int Status_Touch = 1; private static final int Status_Up = 2; private static final int Status_Next = 3; //重新整理width時的漸層量以及時間間距 private static final int Delay_Next = 500; private static final int Delay_Frush = 10; private static final int Pixel_Frush = 8; //按鈕對象 private AnimationButton button; //按鈕對象的長寬與中點座標(長寬為繪製的長寬,而不是控制項的長寬) private int width; private int height; private int centerX; private int centerY; //子狀態變數 private int status = Status_Default; private Handler handler = new Handler(); public DefaultStatus(AnimationButton button, int width, int height) { this.button = button; this.width = width; this.height = height; this.centerX = width / 2; this.centerY = height / 2; } @Override public Status getStatus() { return Status.Default; } @Override public boolean onTouchEvent(MotionEvent mEvent) { switch (mEvent.getAction()) {  case MotionEvent.ACTION_DOWN:  //按下時,切換到按下子狀態  if(status == Status_Default){   status = Status_Touch;   button.invalidate();  }  return true;  case MotionEvent.ACTION_UP:  case MotionEvent.ACTION_CANCEL:  //抬起時,或者移除控制項時,切換到抬起子狀態  if(status == Status_Touch){   status = Status_Up;   button.invalidate();   //過500ms延遲後開始進行伸縮變化   handler.postDelayed(new Runnable() {   @Override   public void run() {    //切換到next子狀態    if(status == Status_Up){    status = Status_Next;    }    if(status == Status_Next){    //若長寬不一致,則繼續漸層,否則改變狀態    if (width >= height) {     width -= Pixel_Frush;     button.invalidate();     handler.postDelayed(this, Delay_Frush);    }else{     button.changeStatus(Status.Progress, width, height, centerX, centerY);    }    }   }   }, Delay_Next);   //響應監聽器   AnimationButton.OnAnimationButtonClickListener listener = button.getOnAnimationButtonClickListener();   if(listener != null){   listener.onClick();   }  }  break; } return false; } @Override public void onDraw(Canvas mCanvas, Paint mPaint) { switch (status) {  case Status_Default:  onDrawDefault(mCanvas, mPaint);  break;  case Status_Touch:  onDrawTouch(mCanvas, mPaint);  break;  case Status_Up:  onDrawUp(mCanvas, mPaint);  break;  case Status_Next:  onDrawNext(mCanvas, mPaint);  break; } } /** * 繪製邊框,分為空白心和實心兩種 * * @param mCanvas 畫布 * @param mPaint 畫筆 * @param style 空心或者實心 * @param padding 邊框補白 */ private void drawRound(Canvas mCanvas, Paint mPaint, Paint.Style style, int padding) { mPaint.setColor(button.getColorBase()); int stroke = padding; if (style == Paint.Style.STROKE) {  mPaint.setStyle(Paint.Style.STROKE);  mPaint.setStrokeWidth(button.getStroke());  stroke += button.getStroke() / 2; } else {  mPaint.setStyle(Paint.Style.FILL); } //繪製邊框 mCanvas.drawRoundRect(new RectF(stroke, stroke, width - stroke, height - stroke), button.getRound(), button.getRound(), mPaint); } /** * 畫文字,有字型大小和顏色的區別 * * @param mCanvas 畫布 * @param mPaint 畫筆 * @param textSize 字型大小 * @param textColor 字型顏色 */ private void drawText(Canvas mCanvas, Paint mPaint, int textSize, int textColor) { mPaint.setColor(textColor); mPaint.setStrokeWidth(button.getStrokeText()); mPaint.setTextSize(textSize); Paint.FontMetrics metrics = mPaint.getFontMetrics(); int textWidth = (int) mPaint.measureText(button.getText()); int baseLine = (int) (height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom); mCanvas.drawText(button.getText(), (width - textWidth) / 2, baseLine, mPaint); } /** * 繪製預設狀態的按鈕 * * @param mCanvas * @param mPaint */ private void onDrawDefault(Canvas mCanvas, Paint mPaint) { drawRound(mCanvas, mPaint, Paint.Style.STROKE, 0); //繪製置中文字 drawText(mCanvas, mPaint, button.getTextSize(), button.getColorBase()); } /** * 繪製按下狀態的按鈕 * * @param mCanvas * @param mPaint */ private void onDrawTouch(Canvas mCanvas, Paint mPaint) { drawRound(mCanvas, mPaint, Paint.Style.FILL, button.getStroke()); //繪製文字,字型要變化 drawText(mCanvas, mPaint, button.getTextSizeTouch(), Color.WHITE); } /** * 繪製抬起狀態的按鈕 * * @param mCanvas * @param mPaint */ private void onDrawUp(Canvas mCanvas, Paint mPaint) { drawRound(mCanvas, mPaint, Paint.Style.FILL, 0); drawText(mCanvas, mPaint, button.getTextSize(), Color.WHITE); } /** * 繪製進入下一狀態的按鈕 * * @param mCanvas * @param mPaint */ private void onDrawNext(Canvas mCanvas, Paint mPaint) { mPaint.setColor(button.getColorBase()); mPaint.setStyle(Paint.Style.FILL); //繪製邊框 if (width >= height) {  mCanvas.drawRoundRect(new RectF(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2),   button.getRound(), button.getRound(), mPaint);  //繪製文字  mPaint.setColor(Color.WHITE);  mPaint.setStrokeWidth(button.getStrokeText());  mPaint.setTextSize(button.getTextSize());  Paint.FontMetrics metrics = mPaint.getFontMetrics();  int textWidth = (int) mPaint.measureText(button.getText());  int baseLine = (int) (centerY + (metrics.bottom - metrics.top) / 2 - metrics.bottom);  mCanvas.drawText(button.getText(), centerX - textWidth / 2, baseLine, mPaint); } else {  mCanvas.drawOval(new RectF(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2), mPaint); } }}

然後是第二個狀態,進度條狀態: 

/** * Created by ccwxf on 2016/2/29. */public class ProgressStatus implements ButtonStatus { //轉圈的子狀態 private static final int Status_Once = 0; private static final int Status_Twice = 1; //轉圈的漸層量 private static final int Delay_Progress = 10; private static final int Angle_Progress = 5; private static final int Angle_Default = -90; private static final int Andle_Full = 270; private AnimationButton button; private int width; private int height; private int centerX; private int centerY; private int radius; private int status = Status_Once; //當前的進度 private float progress = Angle_Default; private Handler handler = new Handler(); public ProgressStatus(AnimationButton button, int width, int height, int centerX, int centerY) { this.button = button; this.width = width; this.height = height; this.centerX = centerX; this.centerY = centerY; //繪製起點是Stroke的中點,若不減去這個值,則onDraw時,會不完整。 this.radius = (width - button.getStrokeProgress()) / 2; startProgress(); } /** * 開始遞迴轉動進度條 */ private void startProgress() { handler.postDelayed(new Runnable() {  @Override  public void run() {  if(progress >= Andle_Full){   //如果是手動結束模式   if(button.getMode() == AnimationButton.Mode.Hand_Finish && button.isProgressStop()){   //改變狀態   button.changeStatus(Status.Finish, width, height, centerX, centerY);   return;   }else{   if(status == Status_Once){    status = Status_Twice;   }else if(status == Status_Twice){    //如果是自動結束模式,則在第二次進度結束時改變狀態    if(button.getMode() == AnimationButton.Mode.Auto_Finish){    //改變狀態    button.changeStatus(Status.Finish, width, height, centerX, centerY);    return;    }else{    status = Status_Once;    }   }   //重設進度   progress = Angle_Default;   }  }  progress += Angle_Progress;  button.invalidate();  handler.postDelayed(this, Delay_Progress);  } }, Delay_Progress); } @Override public Status getStatus() { return Status.Progress; } @Override public boolean onTouchEvent(MotionEvent mEvent) { return false; } @Override public void onDraw(Canvas mCanvas, Paint mPaint) { if(status == Status_Once){  //繪製灰色背景  onDrawCircle(mCanvas, mPaint, button.getColorBack());  //繪製綠色進度  onDrawArc(mCanvas, mPaint, button.getColorBase(), Angle_Default, progress); }else if(status == Status_Twice){  //繪製綠色背景  onDrawCircle(mCanvas, mPaint, button.getColorBase());  //繪製灰色進度  onDrawArc(mCanvas, mPaint, button.getColorBack(), Angle_Default, progress); } } /** * 畫一整個圓作為背景 * @param mCanvas 畫布 * @param mPaint 畫筆 * @param color 顏色 */ private void onDrawCircle(Canvas mCanvas, Paint mPaint, int color){ mPaint.setColor(color); mPaint.setStrokeWidth(button.getStrokeProgress()); mPaint.setStyle(Paint.Style.STROKE); mCanvas.drawCircle(centerX, centerY, radius, mPaint); } /** * 畫一端圓弧 * @param mCanvas 畫布 * @param mPaint 畫筆 * @param color 顏色 * @param start 開始角度 * @param stop 結束角度 */ private void onDrawArc(Canvas mCanvas, Paint mPaint, int color, float start, float stop){ mPaint.setColor(color); mPaint.setStrokeWidth(button.getStrokeProgress()); mPaint.setStyle(Paint.Style.STROKE); //第三個參數是掃過的角度,起點0預設為右邊 mCanvas.drawArc(new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius),  start, stop - start, false, mPaint); }} 

最後一個狀態:

/** * Created by ccwxf on 2016/2/29. */public class FinishStatus implements ButtonStatus { private static final int Status_Stretch = 0; private static final int Status_Finish = 1; private static final int Stroke_Over = 10; private static final String Text_Over = "√"; private static final int Text_Over_Size = 40; private static final int Delay_Stretch = 10; private static final int Pixel_Stretch = 8; private AnimationButton button; private int width; private int height; private int centerX; private int centerY; private int status = Status_Stretch; private Handler handler = new Handler(); public FinishStatus(AnimationButton button, int width, int height, int centerX, int centerY) { this.button = button; this.width = width; this.height = height; this.centerX = centerX; this.centerY = centerY; startStretch(); } /** * 開始伸展背景 */ private void startStretch() { handler.postDelayed(new Runnable() {  @Override  public void run() {  if(width < button.getMaxWidth()){   width += Pixel_Stretch;   button.invalidate();   handler.postDelayed(this, Delay_Stretch);  }else{   width = button.getMaxWidth();   if(status == Status_Stretch){   status = Status_Finish;   }   button.invalidate();  }  } }, Delay_Stretch); } @Override public Status getStatus() { return Status.Finish; } @Override public boolean onTouchEvent(MotionEvent mEvent) { return false; } @Override public void onDraw(Canvas mCanvas, Paint mPaint) { //繪製背景 mPaint.setColor(button.getColorBase()); mPaint.setStyle(Paint.Style.FILL); mCanvas.drawRoundRect(new RectF(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2 ),  button.getRound(), button.getRound(), mPaint); //繪製圖片 if(status == Status_Finish){  mPaint.setColor(Color.WHITE);  mPaint.setStrokeWidth(Stroke_Over);  mPaint.setTextSize(Text_Over_Size);  Paint.FontMetrics metrics = mPaint.getFontMetrics();  int textWidth = (int) mPaint.measureText(Text_Over);  int baseLine = (int) (height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom);  mCanvas.drawText(Text_Over, (width - textWidth) / 2, baseLine, mPaint); } }}

好了上面就是所有的源碼了。雖然被我概括成三個大狀態,但是如果分細一點的話,大概需要9個狀態。在各個大狀態碼裡面的子狀態就是這個了。
 怎麼使用呢,也非常簡單,因為大部分的參數都有內設值了。 

/** * Created by ccwxf on 2016/2/29. */public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final AnimationButton button = (AnimationButton) findViewById(R.id.button);// button.setTextSizeTouch(25); //設定按下時字型的大小,不設定有預設值// button.setStrokeProgress(10); //設定進度條的厚度,不設定有預設值// button.setColorBase(Color.GREEN); //設定整體基色,不設定有預設值// button.setColorBack(Color.GRAY); //設定進度條的背景色,不設定有預設值// button.setStroke(3); //設定邊框的厚度,不設定有預設值// button.setStrokeText(0); //設定文本的厚度,不設定有預設值// button.setTextSize(30); //設定文本的字型大小,不設定有預設值// button.setRound(30); //設定圓角,不設定有預設值 button.setText("登入"); //設定文本,不設定有預設值 button.setMode(AnimationButton.Mode.Hand_Finish); //設定進度條模式,不設定有預設值Mode.Auto_Finish button.setOnAnimationButtonClickListener(new AnimationButton.OnAnimationButtonClickListener() {  @Override  public void onClick() {  //stopProgress方法 僅僅在button.setMode(AnimationButton.Mode.Hand_Finish);之後才有效。  button.stopProgress();  } }); }}

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.