溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕

發(fā)布時(shí)間:2021-06-28 17:24:01 來源:億速云 閱讀:116 作者:Leah 欄目:移動(dòng)開發(fā)

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

1、分析

先來看看原版效果:

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕

該按鈕由3部分組成,分別是圓角矩形、文字、旋轉(zhuǎn)圖標(biāo)。在點(diǎn)擊按鈕后,開始加載數(shù)據(jù),旋轉(zhuǎn)圖標(biāo)發(fā)生旋轉(zhuǎn),數(shù)據(jù)加載完成后,旋轉(zhuǎn)圖標(biāo)復(fù)位并停止旋轉(zhuǎn)。話不多說,開始敲代碼。

2、繪制

這里,我們要繪制的部分有3個(gè),分別是上面提到的圓角矩形、文字、旋轉(zhuǎn)圖標(biāo)。那么這里就為這3部分分別聲明了一些屬性。

要注意的一點(diǎn)是,這個(gè)類中有3個(gè)構(gòu)造函數(shù),因?yàn)橛胁糠謱傩孕枰跇?gòu)造函數(shù)中初始化(也為之后自定義屬性做準(zhǔn)備),所以,將第1個(gè)與第2個(gè)構(gòu)造函數(shù)中的super修改為this。

public class LQRRefreshButton extends View {
  // 圓角矩形屬性
  private int borderColor = Color.parseColor("#fb7299");
  private float borderWidth = 0;
  private float borderRadius = 120;
  // 文字屬性
  private String text = "點(diǎn)擊換一批";
  private int textColor = Color.parseColor("#fb7299");
  private float textSize = 28;
  // 旋轉(zhuǎn)圖標(biāo)屬性
  private int iconSrc = R.mipmap.tag_center_refresh_icon;
  private float iconSize = 28;
  private Bitmap iconBitmap;
  private float space4TextAndIcon = 20;
  // 畫筆
  private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  public LQRRefreshButton(Context context) {
    this(context, null);
  }
  public LQRRefreshButton(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
  }
  public LQRRefreshButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // 將圖標(biāo)資源實(shí)例化為Bitmap
    iconBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.tag_center_refresh_icon);
  }
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 1、畫圓角矩形
    // 2、畫字
    // 3、畫刷新圖標(biāo)
  }
}

接下來著重完成onDraw()方法的實(shí)現(xiàn):

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  // 1、畫圓角矩形
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setColor(borderColor);
  mPaint.setStrokeWidth(borderWidth);
  canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), borderRadius, borderRadius, mPaint);
  // 2、畫字
  mPaint.setTextSize(textSize);
  mPaint.setColor(textColor);
  mPaint.setStyle(Paint.Style.FILL);
  float measureText = mPaint.measureText(text);
  float measureAndIcon = measureText + space4TextAndIcon + iconSize;
  float textStartX = getWidth() / 2 - measureAndIcon / 2;
  float textBaseY = getHeight() / 2 + (Math.abs(mPaint.ascent()) - mPaint.descent()) / 2;
  canvas.drawText(text, textStartX, textBaseY, mPaint);
  // 3、畫刷新圖標(biāo)
  float iconStartX = textStartX + measureText + space4TextAndIcon;
  canvas.drawBitmap(iconBitmap, iconStartX, getHeight() / 2 - iconSize / 2, mPaint);
}

先來看看效果:

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕

我給該控件設(shè)置了寬為200dp,高為100dp。

可以看到效果還不錯(cuò),但還是有一點(diǎn)點(diǎn)問題的,下面就分別說說這3部分是怎么畫的,及存在的小問題。

1)畫圓角矩形

其實(shí)畫圓角矩形很簡(jiǎn)單,設(shè)置好畫筆的樣式、顏色、線粗,再調(diào)用canvas的drawRoundRect()方法即可實(shí)現(xiàn)。

因?yàn)槲覀円嫷膱A角矩形只需要畫線,所以畫筆的樣式便設(shè)置為Paint.Style.STROKE。
canvas的drawRoundRect()方法中,第一個(gè)參數(shù)是繪制范圍,這里就直接按該控件的大小來設(shè)置即可。第二、三個(gè)參數(shù)是x軸和y軸的圓角半徑,第三個(gè)參數(shù)是畫筆(要畫東西當(dāng)然需要畫筆~)。
但你有沒有發(fā)現(xiàn),此時(shí)的 線粗為0(borderWidth=0),矩形線怎么還有?這是因?yàn)楫嫻P的樣式為Paint.Style.STROKE,當(dāng)線粗為0時(shí),還要畫出1px的線,因?yàn)閷?duì)畫筆來說,最小的線粗就是1px。所以,上面的代碼需要做如下改動(dòng):

// 1、畫圓角矩形
if (borderWidth > 0) {
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setColor(borderColor);
  mPaint.setStrokeWidth(borderWidth);
  canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), borderRadius, borderRadius, mPaint);
}

2)畫字

畫字的一般步驟是設(shè)置文字大小、文字顏色、畫筆樣式,繪制起點(diǎn)。其中后2個(gè)最為重要。

畫筆樣式對(duì)畫出的字是有影響的,當(dāng)畫筆樣式為Paint.Style.STROKE時(shí),畫出來的字是鏤空的(不信你可以試試),我們需要的是實(shí)心的字,所以需要修改畫筆的樣式為Paint.Style.FILL。
在安卓中,文字的繪制跟其它繪制是不同的,例如,圓角矩形和旋轉(zhuǎn)圖標(biāo)的繪制起點(diǎn)是左上角,而文字則是按文字左下字為起點(diǎn),也就是按基線(Baseline)來繪制,故需要得到基線起點(diǎn)的坐標(biāo)。

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕

如上圖中,現(xiàn)在要獲得的就是文字左下角的點(diǎn),這要怎么求呢?

先說x,一般需要讓文字居中顯示(跟文字的對(duì)齊方式也有關(guān)系,這里以默認(rèn)的左對(duì)齊為例),所以計(jì)算公式一般為: x = 控件寬度/2 - 文字長(zhǎng)度/2。但我們這個(gè)控件有點(diǎn)不同,它還需要考慮到旋轉(zhuǎn)圖標(biāo)的位置問題,所以x應(yīng)該這么求: x = 控件寬度/2 - (文字長(zhǎng)度+空隙+旋轉(zhuǎn)圖標(biāo)寬度)/2。

// 得到文字長(zhǎng)度
float measureText = mPaint.measureText(text);
// 得到 文字長(zhǎng)度+空隙+旋轉(zhuǎn)圖標(biāo)寬度
float measureAndIcon = measureText + space4TextAndIcon + iconSize;
// 得到文字繪制起點(diǎn)
float textStartX = getWidth() / 2 - measureAndIcon / 2;

再說y,如圖所示:

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕

如果直接用控件的高度的一半作為文字繪制的基線,那么繪制出來的文字肯定偏上,這是因?yàn)锳scent的高度比Descent的高度要高的多,我們?cè)谟?jì)算Baseline時(shí),需要在Ascent中減去Descent的高度得到兩者高度差,再讓控件中心y坐標(biāo)加上(下降)這個(gè)高度差的一半。故:

float textBaseY = getHeight() / 2 + (Math.abs(mPaint.ascent()) - mPaint.descent()) / 2;

3)畫刷新圖標(biāo)

最后就是畫刷新圖標(biāo)了,它是以左上角為起點(diǎn)的,通過canvas的drawBitmap()方法進(jìn)行繪制即可。

但是,有一點(diǎn)需要注意,iconSize是我自己定的一個(gè)大小,并不是圖標(biāo)的實(shí)際大小,所以在往后做旋轉(zhuǎn)動(dòng)畫時(shí)獲取到的旋轉(zhuǎn)中心會(huì)有誤差,將導(dǎo)致圖標(biāo)旋轉(zhuǎn)時(shí)不是按中心進(jìn)行旋轉(zhuǎn)。所以,這里需要對(duì)圖標(biāo)大小進(jìn)行調(diào)整:

public class LQRRefreshButton extends View {
  ...
  public LQRRefreshButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // icon
    iconBitmap = BitmapFactory.decodeResource(getResources(), iconSrc);
    iconBitmap = zoomImg(iconBitmap, iconSize, iconSize);
  }
  public Bitmap zoomImg(Bitmap bm, float newWidth, float newHeight) {
    // 獲得圖片的寬高
    int width = bm.getWidth();
    int height = bm.getHeight();
    // 計(jì)算縮放比例
    float scaleWidth = ((float) newWidth) / width;
    float scaleHeight = ((float) newHeight) / height;
    // 取得想要縮放的matrix參數(shù)
    Matrix matrix = new Matrix();
    matrix.postScale(scaleWidth, scaleHeight);
    // 得到新的圖片
    Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
    return newbm;
  }
  ...
}

3、動(dòng)畫

現(xiàn)在,要實(shí)現(xiàn)旋轉(zhuǎn)圖標(biāo)的旋轉(zhuǎn)功能了。原理就是在canvas繪制圖標(biāo)時(shí),將canvas進(jìn)行旋轉(zhuǎn),canvas旋轉(zhuǎn)著繪制圖標(biāo)也很簡(jiǎn)單,只需要4步:

canvas.save();
canvas.rotate(degress, centerX, centerY);
canvas.drawBitmap(iconBitmap, iconStartX, getHeight() / 2 - iconSize / 2, mPaint);
canvas.restore();

接下來要做的,就是計(jì)算出旋轉(zhuǎn)中心,旋轉(zhuǎn)角度,并不停止的去調(diào)用onDraw()編制圖標(biāo),可以使用ValueAnimator或ObjectAnimator實(shí)現(xiàn)這個(gè)功能,這里選用ObjectAnimator。實(shí)現(xiàn)如下:

public class LQRRefreshButton extends View {
  ...
  private float degress = 0;
  private ObjectAnimator mAnimator;
  public LQRRefreshButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // 旋轉(zhuǎn)動(dòng)畫
    mAnimator = ObjectAnimator.ofObject(this, "degress", new FloatEvaluator(), 360, 0);
    mAnimator.setDuration(2000);
    mAnimator.setRepeatMode(ObjectAnimator.RESTART);
    mAnimator.setInterpolator(new LinearInterpolator());
    mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
  }
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    ...
    // 3、畫刷新圖標(biāo)
    float iconStartX = textStartX + measureText + space4TextAndIcon;
    canvas.save();
    float centerX = iconStartX + iconSize / 2;
    int centerY = getHeight() / 2;
    canvas.rotate(degress, centerX, centerY);
    canvas.drawBitmap(iconBitmap, iconStartX, getHeight() / 2 - iconSize / 2, mPaint);
    canvas.restore();
  }
  public void start() {
    mAnimator.start();
  }
  public void stop() {
    mAnimator.cancel();
    setDegress(0);
  }
  public float getDegress() {
    return degress;
  }
  public void setDegress(float degress) {
    this.degress = degress;
    invalidate();
  }
}

使用ObjectAnimator可以對(duì)任意屬性值進(jìn)行修改,所以需要在該控件中聲明一個(gè)旋轉(zhuǎn)角度變量(degress),并編寫getter和setter方法,還需要在setter方法中調(diào)用invalidate(),這樣才能在角度值發(fā)生變換時(shí),讓控件回調(diào)onDraw()進(jìn)行圖標(biāo)的旋轉(zhuǎn)繪制。ObjectAnimator的使用也不復(fù)雜,這里就不詳細(xì)介紹了。來看下動(dòng)畫效果吧:

Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕

4、自定義屬性

一個(gè)自定義控件,是不能把屬性值寫死在控件里的,所以我們需要自定義屬性,從外界獲取這些屬性值。

1)屬性文件編寫

在attrs.xml中編寫如下代碼:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="LQRRefreshButton">
    <attr name="refresh_btn_borderColor" format="color"/>
    <attr name="refresh_btn_borderWidth" format="dimension"/>
    <attr name="refresh_btn_borderRadius" format="dimension"/>
    <attr name="refresh_btn_text" format="string"/>
    <attr name="refresh_btn_textColor" format="color"/>
    <attr name="refresh_btn_textSize" format="dimension"/>
    <attr name="refresh_btn_iconSrc" format="reference"/>
    <attr name="refresh_btn_iconSize" format="dimension"/>
    <attr name="refresh_btn_space4TextAndIcon" format="dimension"/>
  </declare-styleable>
</resources>

2)屬性值獲取

在控件的第三個(gè)構(gòu)造函數(shù)中獲取這些屬性值:

public class LQRRefreshButton extends View {
  public LQRRefreshButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // 獲取自定義屬性值
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LQRRefreshButton);
    borderColor = ta.getColor(R.styleable.LQRRefreshButton_refresh_btn_borderColor, Color.parseColor("#fb7299"));
    borderWidth = ta.getDimension(R.styleable.LQRRefreshButton_refresh_btn_borderWidth, dipToPx(0));
    borderRadius = ta.getDimension(R.styleable.LQRRefreshButton_refresh_btn_borderRadius, dipToPx(60));
    text = ta.getString(R.styleable.LQRRefreshButton_refresh_btn_text);
    if (text == null)
      text = "";
    textColor = ta.getColor(R.styleable.LQRRefreshButton_refresh_btn_textColor, Color.parseColor("#fb7299"));
    textSize = ta.getDimension(R.styleable.LQRRefreshButton_refresh_btn_textSize, spToPx(14));
    iconSrc = ta.getResourceId(R.styleable.LQRRefreshButton_refresh_btn_iconSrc, R.mipmap.tag_center_refresh_icon);
    iconSize = ta.getDimension(R.styleable.LQRRefreshButton_refresh_btn_iconSize, dipToPx(14));
    space4TextAndIcon = ta.getDimension(R.styleable.LQRRefreshButton_refresh_btn_space4TextAndIcon, dipToPx(10));
    ta.recycle();  
    ...
  }
}

這里有一點(diǎn)需要留意:

ta.getDimension(屬性id, 默認(rèn)值)
1
2

通過TypedArray對(duì)象可以從外界到的的值會(huì)根據(jù)單位(如:dp、sp)的不同自動(dòng)轉(zhuǎn)換成px,但默認(rèn)值的單位是一定的,為px,所以為了符合安卓規(guī)范,不要直接使用px,所以需要手動(dòng)做個(gè)轉(zhuǎn)換。最后還需要調(diào)用recycle()方法回收TypedArray。

3)在布局文件中應(yīng)用

<com.lqr.biliblili.mvp.ui.widget.LQRRefreshButton
  android:id="@+id/btn_refresh"
  android:layout_width="118dp"
  android:layout_height="32dp"
  android:layout_gravity="center"
  android:layout_marginBottom="3dp"
  android:layout_marginTop="8dp"
  app:refresh_btn_borderRadius="25dp"
  app:refresh_btn_borderWidth="1dp"
  app:refresh_btn_iconSize="16dp"
  app:refresh_btn_text="點(diǎn)擊換一批"
  app:refresh_btn_textColor="@color/bottom_text_live"
  app:refresh_btn_textSize="14sp"/>

看完上述內(nèi)容,你們掌握Android中怎么實(shí)現(xiàn)一個(gè)bilibili刷新按鈕的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI