Android開發(fā)仿bilibili刷新按鈕的實(shí)現(xiàn)代碼
一、簡述
最近跟小伙伴一起討論了一下,決定一起仿一個BiliBili的app(包括android端和iOS端),我們并沒有打算把這個項(xiàng)目完全做完,畢竟我們的重點(diǎn)是掌握一些新框架的使用,并在實(shí)戰(zhàn)過程中發(fā)現(xiàn)并彌補(bǔ)自身的不足。
本系列將記錄我(android端)在開發(fā)過程中的一些我覺得有必要記錄的功能實(shí)現(xiàn)而已,并不是完整的從0到1的完整教程,若個別看官大爺覺得不好請出門左拐謝謝。
以下是該項(xiàng)目將會完成的功能。
- 視頻播放功能
- 直播功能
- 彈幕功能
- 換膚功能
- …
本系列文章,將會有記錄以上功能的實(shí)現(xiàn)但不僅僅只有這些,還會有一些其他,比如自定義控件、利用fiddler抓包等,接下來就進(jìn)入本篇的主題——《仿bilibili刷新按鈕的實(shí)現(xiàn)》。
二、實(shí)戰(zhàn)
1、分析
先來看看原版效果:

該按鈕由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個,分別是上面提到的圓角矩形、文字、旋轉(zhuǎn)圖標(biāo)。那么這里就為這3部分分別聲明了一些屬性。
要注意的一點(diǎn)是,這個類中有3個構(gòu)造函數(shù),因?yàn)橛胁糠謱傩孕枰跇?gòu)造函數(shù)中初始化(也為之后自定義屬性做準(zhǔn)備),所以,將第1個與第2個構(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);
}
先來看看效果:

我給該控件設(shè)置了寬為200dp,高為100dp。
可以看到效果還不錯,但還是有一點(diǎn)點(diǎn)問題的,下面就分別說說這3部分是怎么畫的,及存在的小問題。
1)畫圓角矩形
其實(shí)畫圓角矩形很簡單,設(shè)置好畫筆的樣式、顏色、線粗,再調(diào)用canvas的drawRoundRect()方法即可實(shí)現(xiàn)。
因?yàn)槲覀円嫷膱A角矩形只需要畫線,所以畫筆的樣式便設(shè)置為Paint.Style.STROKE。
canvas的drawRoundRect()方法中,第一個參數(shù)是繪制范圍,這里就直接按該控件的大小來設(shè)置即可。第二、三個參數(shù)是x軸和y軸的圓角半徑,第三個參數(shù)是畫筆(要畫東西當(dāng)然需要畫筆~)。
但你有沒有發(fā)現(xiàn),此時的 線粗為0(borderWidth=0),矩形線怎么還有?這是因?yàn)楫嫻P的樣式為Paint.Style.STROKE,當(dāng)線粗為0時,還要畫出1px的線,因?yàn)閷Ξ嫻P來說,最小的線粗就是1px。所以,上面的代碼需要做如下改動:
// 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個最為重要。
畫筆樣式對畫出的字是有影響的,當(dāng)畫筆樣式為Paint.Style.STROKE時,畫出來的字是鏤空的(不信你可以試試),我們需要的是實(shí)心的字,所以需要修改畫筆的樣式為Paint.Style.FILL。
在安卓中,文字的繪制跟其它繪制是不同的,例如,圓角矩形和旋轉(zhuǎn)圖標(biāo)的繪制起點(diǎn)是左上角,而文字則是按文字左下字為起點(diǎn),也就是按基線(Baseline)來繪制,故需要得到基線起點(diǎn)的坐標(biāo)。

如上圖中,現(xiàn)在要獲得的就是文字左下角的點(diǎn),這要怎么求呢?
先說x,一般需要讓文字居中顯示(跟文字的對齊方式也有關(guān)系,這里以默認(rèn)的左對齊為例),所以計(jì)算公式一般為: x = 控件寬度/2 - 文字長度/2。但我們這個控件有點(diǎn)不同,它還需要考慮到旋轉(zhuǎn)圖標(biāo)的位置問題,所以x應(yīng)該這么求: x = 控件寬度/2 - (文字長度+空隙+旋轉(zhuǎn)圖標(biāo)寬度)/2。
// 得到文字長度 float measureText = mPaint.measureText(text); // 得到 文字長度+空隙+旋轉(zhuǎn)圖標(biāo)寬度 float measureAndIcon = measureText + space4TextAndIcon + iconSize; // 得到文字繪制起點(diǎn) float textStartX = getWidth() / 2 - measureAndIcon / 2;
再說y,如圖所示:

如果直接用控件的高度的一半作為文字繪制的基線,那么繪制出來的文字肯定偏上,這是因?yàn)锳scent的高度比Descent的高度要高的多,我們在計(jì)算Baseline時,需要在Ascent中減去Descent的高度得到兩者高度差,再讓控件中心y坐標(biāo)加上(下降)這個高度差的一半。故:
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是我自己定的一個大小,并不是圖標(biāo)的實(shí)際大小,所以在往后做旋轉(zhuǎn)動畫時獲取到的旋轉(zhuǎn)中心會有誤差,將導(dǎo)致圖標(biāo)旋轉(zhuǎn)時不是按中心進(jìn)行旋轉(zhuǎn)。所以,這里需要對圖標(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、動畫
現(xiàn)在,要實(shí)現(xiàn)旋轉(zhuǎn)圖標(biāo)的旋轉(zhuǎn)功能了。原理就是在canvas繪制圖標(biāo)時,將canvas進(jìn)行旋轉(zhuǎn),canvas旋轉(zhuǎn)著繪制圖標(biāo)也很簡單,只需要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)這個功能,這里選用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)動畫
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可以對任意屬性值進(jìn)行修改,所以需要在該控件中聲明一個旋轉(zhuǎn)角度變量(degress),并編寫getter和setter方法,還需要在setter方法中調(diào)用invalidate(),這樣才能在角度值發(fā)生變換時,讓控件回調(diào)onDraw()進(jìn)行圖標(biāo)的旋轉(zhuǎn)繪制。ObjectAnimator的使用也不復(fù)雜,這里就不詳細(xì)介紹了。來看下動畫效果吧:

4、自定義屬性
一個自定義控件,是不能把屬性值寫死在控件里的,所以我們需要自定義屬性,從外界獲取這些屬性值。
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ò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對象可以從外界到的的值會根據(jù)單位(如:dp、sp)的不同自動轉(zhuǎn)換成px,但默認(rèn)值的單位是一定的,為px,所以為了符合安卓規(guī)范,不要直接使用px,所以需要手動做個轉(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"/>
總結(jié)
以上所述是小編給大家介紹的Android 仿bilibili刷新按鈕的實(shí)現(xiàn),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
解析Android中實(shí)現(xiàn)滑動翻頁之ViewFlipper的使用詳解
有一些場景,我們需要向用戶展示一系列的頁面。比如我們正在開發(fā)一個看漫畫的應(yīng)用,可能就需要向用戶展示一張一張的漫畫圖片,用戶使用手指滑動屏幕,可以在前一幅漫畫和后一幅漫畫之間切換。這個時候ViewFlipper就是一個很好的選擇2013-05-05
Android中獲取sha1證書指紋數(shù)據(jù)的方法
大家都知道在Android開發(fā)中,經(jīng)常要獲取sha1證書指紋,所以這篇文章主要介紹在Android中如何使用命令獲取sha1證書指紋數(shù)據(jù)的方法,有需要的可以參考借鑒。2016-09-09
Android中用Bmob實(shí)現(xiàn)短信驗(yàn)證碼功能的方法詳解
本文給大家分享通過第三方平臺Bmob實(shí)現(xiàn)發(fā)送驗(yàn)證碼和校驗(yàn)驗(yàn)證碼的功能,非常不錯,具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09
Android編程自定義Notification實(shí)例分析
這篇文章主要介紹了Android編程自定義Notification的用法,結(jié)合實(shí)例形式簡單分析了自定義Notification的具體功能與實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-12-12
Android獲取手機(jī)號碼和運(yùn)營商信息的方法
這篇文章主要介紹了Android獲取手機(jī)號碼和運(yùn)營商信息的方法,以實(shí)例形式完整講述了獲取手機(jī)號碼和運(yùn)營商信息的技巧,代碼中包含完整的注釋說明,需要的朋友可以參考下2015-01-01
Android下保存簡單網(wǎng)頁到本地(包括簡單圖片鏈接轉(zhuǎn)換)實(shí)現(xiàn)代碼
這篇文章主要介紹了Android下保存簡單網(wǎng)頁到本地(包括簡單圖片鏈接轉(zhuǎn)換)實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-02-02
詳解Android輕量型數(shù)據(jù)庫SQLite
這篇文章主要為大家詳細(xì)介紹了Android輕量型數(shù)據(jù)庫SQLite,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10

