自定義一個廣告倒計時View
今天打開迅雷手機客戶端準備看片的時候,無意間發現這個自定義View,感覺很好看的,實現起來也不麻煩,就嘗試著模仿了一下,花了一天,最后終于搞出來了。因為技術比較菜,所以時間有點長,總之慢慢來吧。
迅雷截圖
自定義View效果圖
-
自定義屬性
底盤的顏色
進度條的顏色
進度條粗細
文字內容
文字顏色
文字大小
<declare-styleable name="CountDownView"> <attr name="background_color" format="color" /> <attr name="border_width" format="dimension" /> <attr name="border_color" format="color" /> <attr name="text" format="string" /> <attr name="text_size" format="dimension" /> <attr name="text_color" format="color" /> </declare-styleable>
-
自定義一個CountDownView,繼承View
public class CountDownView extends View { private static final String TAG = CountDownView.class.getSimpleName(); private static final int BACKGROUND_COLOR = 0x50555555; private static final float BORDER_WIDTH = 15f; private static final int BORDER_COLOR = 0xFF6ADBFE; private static final String TEXT = "跳過廣告"; private static final float TEXT_SIZE = 50f; private static final int TEXT_COLOR = 0xFFFFFFFF; private int backgroundColor; private float borderWidth; private int borderColor; private String text; private int textColor; private float textSize; private Paint circlePaint; private TextPaint textPaint; private Paint borderPaint; private float progress = 135; private StaticLayout staticLayout; private CountDownTimerListener listener; public CountDownView(Context context) { this(context, null); } public CountDownView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountDownView); backgroundColor = ta.getColor(R.styleable.CountDownView_background_color, BACKGROUND_COLOR); borderWidth = ta.getDimension(R.styleable.CountDownView_border_width, BORDER_WIDTH); borderColor = ta.getColor(R.styleable.CountDownView_border_color, BORDER_COLOR); text = ta.getString(R.styleable.CountDownView_text); if (text == null) { text = TEXT; } textSize = ta.getDimension(R.styleable.CountDownView_text_size, TEXT_SIZE); textColor = ta.getColor(R.styleable.CountDownView_text_color, TEXT_COLOR); ta.recycle(); init(); } private void init() { circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setDither(true); circlePaint.setColor(backgroundColor); circlePaint.setStyle(Paint.Style.FILL); textPaint = new TextPaint(); textPaint.setAntiAlias(true); textPaint.setDither(true); textPaint.setColor(textColor); textPaint.setTextSize(textSize); textPaint.setTextAlign(Paint.Align.CENTER); borderPaint = new Paint(); borderPaint.setAntiAlias(true); borderPaint.setDither(true); borderPaint.setColor(borderColor); borderPaint.setStrokeWidth(borderWidth); borderPaint.setStyle(Paint.Style.STROKE); } }
重寫了兩個構造方法,然后對自定義屬性進行了初始化
-
重寫onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { width = staticLayout.getWidth(); } if (heightMode != MeasureSpec.EXACTLY) { height = staticLayout.getHeight(); } setMeasuredDimension(width, height); }
這個不多說,重寫onMeasure方法是必須實現的,重寫此方法的目的是測量控件的實際大小,因為有的時候用戶填寫的width和height是wrap_content,懂了吧,當wrap_content的時候,我們就需要測量控件的實際大小了
-
重寫onDraw方法
@Override protected void onDraw(Canvas canvas) { int width = getMeasuredWidth(); int height = getMeasuredHeight(); int min = Math.min(width, height); //畫底盤 canvas.drawCircle(width / 2, height / 2, min / 2, circlePaint); //畫邊框 RectF rectF; if (width > height) { rectF = new RectF(width / 2 - min / 2 + borderWidth / 2, 0 + borderWidth / 2, width / 2 + min / 2 - borderWidth / 2, height - borderWidth / 2); } else { rectF = new RectF(borderWidth / 2, height / 2 - min / 2 + borderWidth / 2, width - borderWidth / 2, height / 2 - borderWidth / 2 + min / 2); } canvas.drawArc(rectF, -90, progress, false, borderPaint); //畫居中的文字 canvas.translate(width / 2, height / 2 - staticLayout.getHeight() / 2); staticLayout.draw(canvas); }
這里有必要提一下的是StaticLayout這個類。如果我們用canvas.drawText這個方法,也是可以的,但是有個問題,這個方法寫出來的文字是單行的,不會回行顯示,但是迅雷中的“跳過廣告”4個字是分兩行顯示的,這個時候我們就需要用到StaticLayout這個類了。這個類使用起來也很簡單,具體的使用方法請參照其它博客。
其實到這里,整個控件已經寫完了,但是我們希望這個控件在開始計時的時候給我們一個提示,在結束的時候再給我們一個提示,好讓我們進行額外的操作。
我們的解決辦法是給外界暴露一個接口,直接看代碼吧!
public void start() {
if (listener != null) {
listener.onStartCount();
}
CountDownTimer countDownTimer = new CountDownTimer(3600, 36) {
@Override
public void onTick(long millisUntilFinished) {
progress = ((3600 - millisUntilFinished) / 3600f) * 360;
Log.d(TAG, "progress:" + progress);
invalidate();
}
@Override
public void onFinish() {
progress = 360;
invalidate();
if (listener != null) {
listener.onFinishCount();
}
}
}.start();
}
public void setCountDownTimerListener(CountDownTimerListener listener) {
this.listener = listener;
}
public interface CountDownTimerListener {
void onStartCount();
void onFinishCount();
}
我們定義了一個接口,里面有兩個方法,onStartCount()和onFinishCount()
public void start()這個方法是用來啟動計時器的,調用這個方法之后,計時程序就會開始了,開始的時候會調用onStartCount這個接口,然后計時的過程中會根據process(進度)不斷地重繪整個View,達到動畫效果,最后結束的時候會調用onFinishCount這個接口
看一下整體的代碼吧:
package com.example.customview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.CountDownTimer;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* Created on 2016/10/8.
*/
public class CountDownView extends View {
private static final String TAG = CountDownView.class.getSimpleName();
private static final int BACKGROUND_COLOR = 0x50555555;
private static final float BORDER_WIDTH = 15f;
private static final int BORDER_COLOR = 0xFF6ADBFE;
private static final String TEXT = "跳過廣告";
private static final float TEXT_SIZE = 50f;
private static final int TEXT_COLOR = 0xFFFFFFFF;
private int backgroundColor;
private float borderWidth;
private int borderColor;
private String text;
private int textColor;
private float textSize;
private Paint circlePaint;
private TextPaint textPaint;
private Paint borderPaint;
private float progress = 0;
private StaticLayout staticLayout;
private CountDownTimerListener listener;
public CountDownView(Context context) {
this(context, null);
}
public CountDownView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountDownView);
backgroundColor = ta.getColor(R.styleable.CountDownView_background_color, BACKGROUND_COLOR);
borderWidth = ta.getDimension(R.styleable.CountDownView_border_width, BORDER_WIDTH);
borderColor = ta.getColor(R.styleable.CountDownView_border_color, BORDER_COLOR);
text = ta.getString(R.styleable.CountDownView_text);
if (text == null) {
text = TEXT;
}
textSize = ta.getDimension(R.styleable.CountDownView_text_size, TEXT_SIZE);
textColor = ta.getColor(R.styleable.CountDownView_text_color, TEXT_COLOR);
ta.recycle();
init();
}
private void init() {
circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
circlePaint.setColor(backgroundColor);
circlePaint.setStyle(Paint.Style.FILL);
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setDither(true);
textPaint.setColor(textColor);
textPaint.setTextSize(textSize);
textPaint.setTextAlign(Paint.Align.CENTER);
borderPaint = new Paint();
borderPaint.setAntiAlias(true);
borderPaint.setDither(true);
borderPaint.setColor(borderColor);
borderPaint.setStrokeWidth(borderWidth);
borderPaint.setStyle(Paint.Style.STROKE);
int textWidth = (int) textPaint.measureText(text.substring(0, (text.length() + 1) / 2));
staticLayout = new StaticLayout(text, textPaint, textWidth, Layout.Alignment.ALIGN_NORMAL, 1F, 0, false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
width = staticLayout.getWidth();
}
if (heightMode != MeasureSpec.EXACTLY) {
height = staticLayout.getHeight();
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int min = Math.min(width, height);
//畫底盤
canvas.drawCircle(width / 2, height / 2, min / 2, circlePaint);
//畫邊框
RectF rectF;
if (width > height) {
rectF = new RectF(width / 2 - min / 2 + borderWidth / 2, 0 + borderWidth / 2, width / 2 + min / 2 - borderWidth / 2, height - borderWidth / 2);
} else {
rectF = new RectF(borderWidth / 2, height / 2 - min / 2 + borderWidth / 2, width - borderWidth / 2, height / 2 - borderWidth / 2 + min / 2);
}
canvas.drawArc(rectF, -90, progress, false, borderPaint);
//畫居中的文字
// canvas.drawText("稍等片刻", width / 2, height / 2 - textPaint.descent() + textPaint.getTextSize() / 2, textPaint);
canvas.translate(width / 2, height / 2 - staticLayout.getHeight() / 2);
staticLayout.draw(canvas);
}
public void start() {
if (listener != null) {
listener.onStartCount();
}
CountDownTimer countDownTimer = new CountDownTimer(3600, 36) {
@Override
public void onTick(long millisUntilFinished) {
progress = ((3600 - millisUntilFinished) / 3600f) * 360;
Log.d(TAG, "progress:" + progress);
invalidate();
}
@Override
public void onFinish() {
progress = 360;
invalidate();
if (listener != null) {
listener.onFinishCount();
}
}
}.start();
}
public void setCountDownTimerListener(CountDownTimerListener listener) {
this.listener = listener;
}
public interface CountDownTimerListener {
void onStartCount();
void onFinishCount();
}
}
這次比較懶,沒有寫什么注釋
最后,我們來看看如何使用這個自定義View,我們現在布局文件中引用這個布局
<com.example.customview.CountDownView
android:id="@+id/countDownView"
android:layout_width="50dp"
android:layout_height="50dp"
app:background_color="#22000000"
app:border_color="#55B8E2"
app:border_width="2dp"
app:text_size="12dp" />
定義了寬度,高度,背景色,邊框顏色,邊框粗細,文字大小
然后到MainActivity中去使用這個自定義View
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private long lastTime;
private CountDownView count_down_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
count_down_view = (CountDownView) findViewById(R.id.countDownView);
count_down_view.setCountDownTimerListener(new CountDownView.CountDownTimerListener() {
@Override
public void onStartCount() {
Toast.makeText(getApplicationContext(),"開始計時",Toast.LENGTH_SHORT).show();
}
@Override
public void onFinishCount() {
Toast.makeText(getApplicationContext(),"計時結束",Toast.LENGTH_SHORT).show();
}
});
count_down_view.setOnClickListener(this);
}
//連按兩次退出應用程序
@Override
public void onBackPressed() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastTime < 2 * 1000) {
super.onBackPressed();
} else {
Toast.makeText(this, "請再按一次", Toast.LENGTH_SHORT).show();
lastTime = currentTime;
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.countDownView:
count_down_view.start();
break;
}
}
}
中間穿插著一些連按兩次Back鍵退出應用程序的代碼,所以代碼量比較多,其實使用起來還是很方便的
來自:http://www.jianshu.com/p/3db73ba78882