Android自定義帶有圓形進度條的可長按控件功能
這幾天有在學(xué)習(xí)Jetpack中CameraX的內(nèi)容,在拍攝視頻的時候想著做一個自定義帶有進度條的可長按控件,用來顯示拍攝進度,故記錄下來與大家分享!效果如下:
(篇幅過長是因為有代碼解析過程,可直接到最后查看完整代碼)

這個控件較為簡易,從效果中可以看出,控件模擬了單擊拍照,長按可以錄制視頻的功能,中途松手或者時間到都可以停止錄制
思路很簡單,使用簡單的畫筆工具就可以完成這個控件
- 繼承自View
- 定義自定義屬性并獲取
- 定義填充樣式的畫筆
- onMeasure中測量大小,onDraw中繪制圓與扇形
- 監(jiān)聽長按監(jiān)聽開始定時器并刷新畫布,監(jiān)聽觸摸事件進行結(jié)束的回調(diào)
以上就是全部的思路了,代碼拆解如下:
(一)繼承自View并實現(xiàn)構(gòu)造方法,代碼如下:
public class LongClickView extends View {
public int DEFAULT_MAX_SECONDS = 15;
public int DEFAULT_ANNULUS_WIDTH = 5;
public int DEFAULT_ANNULUS_COLOR;
public int DEFAULT_RATE = 50;
private Paint mSmallCirclePaint;
private Paint mMiddenCirclePaint;
private Paint mBigCirclePaint;
private Paint mAngleCirclePaint;
private int mWidthSize;
private Timer mTimer;//計時器
private AtomicInteger mCount = new AtomicInteger(0);
private MyClickListener mMyClickListener;
private boolean mIsFinish = true;
private long mStartTime;//點擊的時間
private long mEndTime;//點擊結(jié)束的時間
private int mMaxSeconds;
private int mDelayMilliseconds;
private int mAnnulusColor;
private float mAnnulusWidth;
public interface MyClickListener {
void longClickFinish();//長按結(jié)束
void singleClickFinish();//單擊結(jié)束
}
public void setMyClickListener(MyClickListener myClickListener) {
mMyClickListener = myClickListener;
}
public LongClickView(Context context) {
this(context, null);
}
public LongClickView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LongClickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getAttrs(context, attrs);
initView();
}
}(二)定義并獲取自定義屬性,屬性以及獲取屬性代碼如下:
attr_long_click_view.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LongClickView">
<attr name="maxSeconds" format="integer" />
<attr name="annulusWidth" format="integer" />
<attr name="annulusColor" format="color" />
<attr name="delayMilliseconds" format="integer" />
</declare-styleable>
</resources> private void getAttrs(Context context, @Nullable AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LongClickView);
//maxSeconds 最大的秒數(shù)
mMaxSeconds = typedArray.getInt(R.styleable.LongClickView_maxSeconds, DEFAULT_MAX_SECONDS);
//annulusWidth 圓環(huán)的寬度
mAnnulusWidth = typedArray.getInt(R.styleable.LongClickView_annulusWidth, DEFAULT_ANNULUS_WIDTH);
//annulusColor 圓環(huán)的顏色
DEFAULT_ANNULUS_COLOR = context.getResources().getColor(R.color.color_grey);
mAnnulusColor = typedArray.getColor(R.styleable.LongClickView_annulusColor, DEFAULT_ANNULUS_COLOR);
//delayMilliseconds 進度條隔多少時間走一次,值越小走的越快,顯得更流暢
mDelayMilliseconds = typedArray.getInt(R.styleable.LongClickView_delayMilliseconds, DEFAULT_RATE);
}(三)定義畫筆工具 的代碼如下:
private void initView() {
mBigCirclePaint = new Paint();
mSmallCirclePaint = new Paint();
mMiddenCirclePaint = new Paint();
mAngleCirclePaint = new Paint();
mBigCirclePaint.setStyle(Paint.Style.FILL);
mBigCirclePaint.setColor(Color.LTGRAY);
mBigCirclePaint.setAntiAlias(true);
mBigCirclePaint.setStrokeWidth(5);
mSmallCirclePaint.setStrokeWidth(5);
mSmallCirclePaint.setAntiAlias(true);
mSmallCirclePaint.setColor(Color.WHITE);
mSmallCirclePaint.setStyle(Paint.Style.FILL);
mMiddenCirclePaint.setStrokeWidth(5);
mMiddenCirclePaint.setAntiAlias(true);
mMiddenCirclePaint.setColor(Color.LTGRAY);
mMiddenCirclePaint.setStyle(Paint.Style.FILL);
mAngleCirclePaint.setStrokeWidth(5);
mAngleCirclePaint.setAntiAlias(true);
mAngleCirclePaint.setColor(mAnnulusColor);
mAngleCirclePaint.setStyle(Paint.Style.FILL);
...//這里是長按監(jiān)聽
}(四)onMeasure中測量大小,onDraw中繪制圓與扇形,代碼如下:
onMeasure中,如果沒有定義實際寬高就會使用父組件的寬高,如果有實際寬高便會使用自己的寬高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidthSize, mWidthSize);
}onDraw中,一共有三層圓形填充繪制以及一層扇形填充繪制,先繪制最外層的灰色圓形,再根據(jù)此時的進度繪制一定角度的扇形,然后覆蓋一層灰色的圓形,最后在覆蓋上一層白色的中心圓,并且在繪制過程以及繪制結(jié)束時的中心圓半徑不同。代碼如下:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2, mBigCirclePaint);//最外層的填充圓
RectF rectF = new RectF(0, 0, mWidthSize, mWidthSize);//進度扇形
if (mCount.get() > 0) {
//求出每一次定時器執(zhí)行所繪制的扇形度數(shù)
float perAngle = 360f / mMaxSeconds / (1000f / mDelayMilliseconds);
canvas.drawArc(rectF, 0, perAngle * mCount.get(), true, mAngleCirclePaint);
}
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mMiddenCirclePaint);//中間一層灰色的圓
//最后繪制中心圓
if (mIsFinish) {
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mSmallCirclePaint);
} else {
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 8, mSmallCirclePaint);
}
super.onDraw(canvas);
}(五)監(jiān)聽長按監(jiān)聽開始定時器并刷新畫布,監(jiān)聽觸摸事件進行結(jié)束的回調(diào),定時器使用的是Timer類,當(dāng)時間超過自定義的最大秒數(shù)時就會自動停止,并定時刷新畫布,代碼如下:
this.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mIsFinish = false;
mCount.set(0);
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
mCount.addAndGet(1);
invalidate();
if (mCount.get() * mDelayMilliseconds >= mMaxSeconds * 1000) {
mCount.set(0);
this.cancel();
invalidate();
mIsFinish = true;
if (mMyClickListener != null) {
mMyClickListener.longClickFinish();
}
}
}
}, 0, mDelayMilliseconds);
return true;
}
}); @Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
mEndTime = System.currentTimeMillis();
new MyAsyncTask().execute();
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
mStartTime = System.currentTimeMillis();
}
return super.onTouchEvent(event);
}將定時器停止與停止后的判斷邏輯放在AsyncTask中編寫,確保定時器不會繼續(xù)處理邏輯之后再做判斷
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
if (mTimer != null) {
mTimer.cancel();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
//使用時間戳的差來判斷是單擊或者長按
if (mEndTime - mStartTime > 1000) {
//防止在自動結(jié)束后松開手指又重新調(diào)用了一次長按結(jié)束的回調(diào)
if (!mIsFinish) {
if (mMyClickListener != null) {
mMyClickListener.longClickFinish();
}
}
} else {
//若是單擊就清除進度條
mCount.set(0);
invalidate();
if (mMyClickListener != null) {
mMyClickListener.singleClickFinish();
}
}
mIsFinish = true;
}
}
結(jié)束后的回調(diào)類代碼如下:
public interface MyClickListener {
void longClickFinish();//長按結(jié)束
void singleClickFinish();//單擊結(jié)束
}最后,完整的代碼如下,自定義屬性上方有貼出來代碼:
public class LongClickView extends View {
public int DEFAULT_MAX_SECONDS = 15;
public int DEFAULT_ANNULUS_WIDTH = 5;
public int DEFAULT_ANNULUS_COLOR;
public int DEFAULT_RATE = 50;
private Paint mSmallCirclePaint;
private Paint mMiddenCirclePaint;
private Paint mBigCirclePaint;
private Paint mAngleCirclePaint;
private int mWidthSize;
private Timer mTimer;//計時器
private AtomicInteger mCount = new AtomicInteger(0);
private MyClickListener mMyClickListener;
private boolean mIsFinish = true;
private long mStartTime;//點擊的時間
private long mEndTime;//點擊結(jié)束的時間
private int mMaxSeconds;
private int mDelayMilliseconds;
private int mAnnulusColor;
private float mAnnulusWidth;
public interface MyClickListener {
void longClickFinish();//長按結(jié)束
void singleClickFinish();//單擊結(jié)束
}
public void setMyClickListener(MyClickListener myClickListener) {
mMyClickListener = myClickListener;
}
public LongClickView(Context context) {
this(context, null);
}
public LongClickView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LongClickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getAttrs(context, attrs);
initView();
}
private void getAttrs(Context context, @Nullable AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LongClickView);
//maxSeconds 最大的秒數(shù)
mMaxSeconds = typedArray.getInt(R.styleable.LongClickView_maxSeconds, DEFAULT_MAX_SECONDS);
//annulusWidth 圓環(huán)的寬度
mAnnulusWidth = typedArray.getInt(R.styleable.LongClickView_annulusWidth, DEFAULT_ANNULUS_WIDTH);
//annulusColor 圓環(huán)的顏色
DEFAULT_ANNULUS_COLOR = context.getResources().getColor(R.color.color_grey);
mAnnulusColor = typedArray.getColor(R.styleable.LongClickView_annulusColor, DEFAULT_ANNULUS_COLOR);
//delayMilliseconds 進度條隔多少時間走一次,值越小走的越快,顯得更流暢
mDelayMilliseconds = typedArray.getInt(R.styleable.LongClickView_delayMilliseconds, DEFAULT_RATE);
}
private static final String TAG = "LongClickView";
private void initView() {
mBigCirclePaint = new Paint();
mSmallCirclePaint = new Paint();
mMiddenCirclePaint = new Paint();
mAngleCirclePaint = new Paint();
mBigCirclePaint.setStyle(Paint.Style.FILL);
mBigCirclePaint.setColor(Color.LTGRAY);
mBigCirclePaint.setAntiAlias(true);
mBigCirclePaint.setStrokeWidth(5);
mSmallCirclePaint.setStrokeWidth(5);
mSmallCirclePaint.setAntiAlias(true);
mSmallCirclePaint.setColor(Color.WHITE);
mSmallCirclePaint.setStyle(Paint.Style.FILL);
mMiddenCirclePaint.setStrokeWidth(5);
mMiddenCirclePaint.setAntiAlias(true);
mMiddenCirclePaint.setColor(Color.LTGRAY);
mMiddenCirclePaint.setStyle(Paint.Style.FILL);
mAngleCirclePaint.setStrokeWidth(5);
mAngleCirclePaint.setAntiAlias(true);
mAngleCirclePaint.setColor(mAnnulusColor);
mAngleCirclePaint.setStyle(Paint.Style.FILL);
this.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mIsFinish = false;
mCount.set(0);
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
mCount.addAndGet(1);
invalidate();
if (mCount.get() * mDelayMilliseconds >= mMaxSeconds * 1000) {
mCount.set(0);
this.cancel();
invalidate();
mIsFinish = true;
if (mMyClickListener != null) {
mMyClickListener.longClickFinish();
}
}
}
}, 0, mDelayMilliseconds);
return true;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidthSize, mWidthSize);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2, mBigCirclePaint);//最外層的填充圓
RectF rectF = new RectF(0, 0, mWidthSize, mWidthSize);//進度扇形
if (mCount.get() > 0) {
//求出每一次定時器執(zhí)行所繪制的扇形度數(shù)
float perAngle = 360f / mMaxSeconds / (1000f / mDelayMilliseconds);
canvas.drawArc(rectF, 0, perAngle * mCount.get(), true, mAngleCirclePaint);
}
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mMiddenCirclePaint);//中間一層灰色的圓
//最后繪制中心圓
if (mIsFinish) {
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mSmallCirclePaint);
} else {
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 8, mSmallCirclePaint);
}
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
mEndTime = System.currentTimeMillis();
new MyAsyncTask().execute();
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
mStartTime = System.currentTimeMillis();
}
return super.onTouchEvent(event);
}
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
if (mTimer != null) {
mTimer.cancel();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
//使用時間戳的差來判斷是單擊或者長按
if (mEndTime - mStartTime > 1000) {
//防止在結(jié)束后松開手指有重新調(diào)用了一次長按結(jié)束的回調(diào)
if (!mIsFinish) {
if (mMyClickListener != null) {
mMyClickListener.longClickFinish();
}
}
} else {
mCount.set(0);
invalidate();
if (mMyClickListener != null) {
mMyClickListener.singleClickFinish();
}
}
mIsFinish = true;
}
}
}使用的代碼如下:
activity_long_click_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.example.customerview.long_click_view.LongClickView
android:id="@+id/long_click_view"
android:layout_width="100dp"
android:layout_height="wrap_content"
app:annulusColor="@color/color_2196F3"
app:annulusWidth="20"
app:delayMilliseconds="40"
app:maxSeconds="4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="長按錄制視頻,單擊拍照"
android:textColor="@color/colorBlack"
android:textSize="20dp" />
</LinearLayout>LongClickViewActivity.java
mLongClickView.setMyClickListener(new LongClickView.MyClickListener() {
@Override
public void longClickFinish() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LongClickViewActivity.this, "長按結(jié)束", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void singleClickFinish() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LongClickViewActivity.this, "單擊結(jié)束", Toast.LENGTH_SHORT).show();
}
});
}
});到此這篇關(guān)于Android自定義帶有圓形進度條的可長按控件功能的文章就介紹到這了,更多相關(guān)Android圓形進度條內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中RecyclerView的item寬高問題詳解
RecyclerView出現(xiàn)已經(jīng)有一段時間了,相信大家肯定不陌生了,下面這篇文章主要給大家介紹了關(guān)于Android中RecyclerView的item寬高問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08
Android仿銀行客戶簽名并且保存簽名的截圖文件并命名為本地時間
本文通過實例代碼給大家介紹了Android仿銀行客戶簽名并且保存簽名的截圖文件并命名為本地時間,需要的朋友可以參考下2017-07-07
android webview中使用Java調(diào)用JavaScript方法并獲取返回值
這篇文章主要介紹了android webview中使用Java調(diào)用JavaScript方法并獲取返回值,本文直接給出代碼示例,需要的朋友可以參考下2015-03-03
Android列表實現(xiàn)(1)_數(shù)組列表實例介紹
最近開始學(xué)習(xí)android的ui,先上幾個相關(guān)的例子,后續(xù)還會有更新,感興趣的朋友可以研究下2012-12-12
詳解Android權(quán)限管理之Android 6.0運行時權(quán)限及解決辦法
本篇文章主要介紹Android權(quán)限管理之Android 6.0運行時權(quán)限及解決辦法,具有一定的參考價值,有興趣的可以了解一下。2016-11-11

