自定義一個廣告倒計時View

Generics 8年前發布 | 19K 次閱讀 安卓開發 Android開發 移動開發

今天打開迅雷手機客戶端準備看片的時候,無意間發現這個自定義View,感覺很好看的,實現起來也不麻煩,就嘗試著模仿了一下,花了一天,最后終于搞出來了。因為技術比較菜,所以時間有點長,總之慢慢來吧。

迅雷截圖

自定義View效果圖

  1. 自定義屬性

    底盤的顏色

    進度條的顏色

    進度條粗細

    文字內容

    文字顏色

    文字大小

    <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>
  2. 自定義一個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);
     }
    }

    重寫了兩個構造方法,然后對自定義屬性進行了初始化

  3. 重寫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的時候,我們就需要測量控件的實際大小了

  4. 重寫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

 

 本文由用戶 Generics 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!