Android自定義View實現(xiàn)旋轉(zhuǎn)的圓形圖片
自定義View是android開發(fā)的一個重要技能,用android提供的2/3D繪制相關(guān)類可以實現(xiàn)非常多炫酷的效果,需要實打?qū)嵉木幊袒A(chǔ)。
但是自定義View又是我的弱項,所以最近都在摸索、練習(xí)自定義View。今天我寫了一個圓形圖片,同時不斷勻速旋轉(zhuǎn)的RotateCircleImageView。實現(xiàn)方法是自己想的,但肯定不是最好的實現(xiàn)方法。
自定義View分四步。
一:自定義屬性;
二:創(chuàng)建自定義View,在構(gòu)造方法中拿到自定義屬性;
三:重寫onMeasure方法;
四:重寫onDraw方法
先來個效果圖

先在res/values/下新建attrs.xml
自定義屬性
<declare-styleable name="RotateCircleImageView">
<attr name="image" format="reference" />
<attr name="rotate_sd" format="float" />
<attr name="rotate_fx" format="integer" />
<attr name="isRotate" format="boolean" />
<attr name="circle_back_width" format="dimension" />
<attr name="circle_back_color" format="color" />
</declare-styleable>
創(chuàng)建RotateCircleImageView
public RotateCircleImageView(Context context) {
this(context, null);
}
public RotateCircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RotateCircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initData();
}
重寫View的三個構(gòu)造函數(shù),用一參的調(diào)用二參的,用二參的調(diào)用三參的。在三參的構(gòu)造里初始化參數(shù)。
private Bitmap image;
private Bitmap tempImage;
private Paint paint;
private int bkWidth;//黑色圓邊框的寬度
private int rotate_fx=0;//旋轉(zhuǎn)方向 0=順時針 1=逆時針
private float rotateSD = 0.8f;//每次旋轉(zhuǎn)的角度--建議范圍0.1f-1,否則會抖動
private boolean isRotate = false;//控制是否旋轉(zhuǎn)
private void initData() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.RotateCircleImageView, defStyleAttr, 0);//用這個類獲得自定義的屬性
paint.setColor(typedArray.getColor(R.styleable.RotateCircleImageView_circle_back_color,
Color.BLACK));
tempImage = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(
R.styleable.RotateCircleImageView_image, R.mipmap.ic_launcher));
bkWidth = typedArray.getDimensionPixelSize(R.styleable.
RotateCircleImageView_circle_back_width,
DensityUtils.dp2px(context, 100));//黑色邊框的寬度,DensityUtils是我的一個工具類,將dp轉(zhuǎn)換成px的
rotateSD = typedArray.getFloat(R.styleable.RotateCircleImageView_rotate_sd, 0.8f);
rotate_fx = typedArray.getInt(R.styleable.RotateCircleImageView_rotate_fx, 0);
isRotate = typedArray.getBoolean(R.styleable.RotateCircleImageView_isRotate, true);
}
重寫測量方法:主要是測量包裹內(nèi)容的情況下寬度和高度的值
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);//分別拿到寬高的大小和測量模式
int mWidth;//最終寬度
int mHeight;//最終高度
int yy_width = widthSize;//預(yù)測寬度,先假設(shè)它等于指定大小或填充窗體
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;//如果是指定大小或填充窗體(以后直接說成指定大?。?直接設(shè)置最終寬度
} else {
yy_width=tempImage.getWidth();//如果是包裹內(nèi)容,則預(yù)測寬度等于圖片寬度
mWidth = yy_width + getPaddingLeft() + getPaddingRight();//最終寬度等于預(yù)測寬度加 左右Padding寬度
}
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;//同上
} else {
mHeight = getPaddingTop() + getPaddingBottom() + yy_width;//最終高度等于預(yù)測寬度加 上下Padding寬度
//目的是讓控件的寬高相等,但Padding是可以由用戶自由指定的,所以再加上padding
}
if (tempImage.getHeight() < tempImage.getWidth()) {
//這里用Bitmap類提供的縮放方法把圖片縮放成指定大小,如果圖片高度比寬度小,則強制拉伸
image = Bitmap.createScaledBitmap(tempImage, yy_width - bkWidth,
yy_width - bkWidth, false);
} else {
//這里用Bitmap類提供的縮放方法把圖片縮放成指定大小(寬度等于預(yù)測的寬度,高度按比例縮放)
//該方法根據(jù)參數(shù)的寬高強制縮放圖片,所以這里根據(jù)寬度算出縮放后的高度
image = Bitmap.createScaledBitmap(tempImage, yy_width - bkWidth,(int) (tempImage.getHeight() /
(((float) tempImage.getWidth()) / yy_width) - bkWidth), false);
}
setMeasuredDimension(mWidth, mHeight);//設(shè)置View的寬高,測量結(jié)束
}
假如寬度是指定大小,我希望高度根據(jù)這個大小按比例縮放,那么我需要拿到圖片原始大小,所以需要一個tempImage,為什么寫一個臨時的Bitmap?因為我測試的時候發(fā)現(xiàn) 假如我用這個image直接把Bitmap.createScaledBitmap(image,xx,xx,false);的返回值賦給image的話,即使我在這行代碼前去用image.getWidth()和Image.getHeight(),返回的值都已經(jīng)變成縮放后的大小,而不是原始大小,這讓我感到很奇怪。難道BItmap的getWidth和getHeight是異步的嗎?希望知道的人幫我解答。
最后重寫onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth() / 2, getWidth() / 2 , getWidth() / 2, paint);//繪制黑色圓
canvas.drawBitmap(getCircleBitmap(image, image.getWidth(), rotateSD),
getWidth() / 2 - image.getWidth() / 2,
getHeight() / 2 - image.getWidth() / 2, paint);//繪制圓形圖片
if (isRotate) {
handler.postDelayed(runnable, 16);//16毫秒后啟動子線程
}
}
getCircleBitmap方法和子線程的代碼:
private Bitmap bitmap;
private boolean isCreateBitmap = false;
private Canvas canvas;
private PorterDuffXfermode pdf;
private Paint bitmapPaint;
private Bitmap getCircleBitmap(Bitmap image, int width, float rotate) {
if (!isCreateBitmap) {//節(jié)約資源所以這些代碼只需要執(zhí)行一次
bitmapPaint = new Paint();
bitmapPaint.setAntiAlias(true);//抗鋸齒
bitmapPaint.setDither(true);//忘了是啥....反正效果好點
bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);//創(chuàng)建一個指定寬高的空白bitmap
isCreateBitmap = true;
canvas = new Canvas(bitmap);//用那個空白bitmap創(chuàng)建一個畫布
canvas.drawCircle(width / 2, width / 2, width / 2, bitmapPaint);//在畫布上畫個圓
pdf = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);//創(chuàng)建一個混合模式為保留后者相交的部分
}
bitmapPaint.setXfermode(pdf);//設(shè)置混合模式
if (rotate_fx==0) {
canvas.rotate(rotate, width / 2, width / 2);//順時針
} else {//旋轉(zhuǎn)畫布:意思是下一次繪制的內(nèi)容會被旋轉(zhuǎn)這么多個角度
canvas.rotate(-rotate, width / 2, width / 2);//逆時針
}
canvas.drawBitmap(image, 0, 0, bitmapPaint);//繪制圖片,(圖片會被旋轉(zhuǎn))
bitmapPaint.setXfermode(null);
return bitmap;//這個bitmap在畫布中被旋轉(zhuǎn),畫圓,返回后就是一個圓形的bitmap
}
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
@Override
public void run() {
invalidate();//刷新界面
}
};
在第一次執(zhí)行onDraw方法的時候得到的是一個旋轉(zhuǎn)了0.8度的bitmap,然后16毫秒后啟動子線程刷新,再次執(zhí)行onDraw,得到一個再次旋轉(zhuǎn)0.8度的bitmap,以此類推,所以不斷旋轉(zhuǎn)。想要轉(zhuǎn)的快一點就把每次旋轉(zhuǎn)的角度調(diào)大一點,但是不能太大,否則效果很不好。一卡一卡的。這樣就完成了這個自定義view,非常簡單,但是我卻折騰了好久,主要還是測量的時候不夠細(xì)心。實現(xiàn)方法都是自己整出來的,如果有更好的實現(xiàn)方法歡迎告知。
最后再暴露兩個方法給外部
public void startRotate() {//開始旋轉(zhuǎn)
if (!isRotate) {
this.isRotate = true;
invalidate();
}
}
public void stopRotate() {//暫停旋轉(zhuǎn)
isRotate = false;
}
然后可以在布局里試試了:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:fitsSystemWindows="true"
android:orientation="vertical">
<com.as.liji.jishiben.view.RotateCircleImageView
android:id="@+id/rcv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:circle_back_width="80dp"
app:image="@mipmap/sm"
app:isRotate="false"
app:rotate_fx="0"
app:rotate_sd="0.5" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/rcv"
android:layout_centerHorizontal="true"
android:ellipsize="marquee"
android:text="正在播放:蜘蛛俠插曲--Hold On" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="startRotate"
android:text="開始" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="stopRotate"
android:text="暫停" />
</LinearLayout>
</RelativeLayout>
在activity中拿到控件,重寫兩個按鈕的點擊事件方法:
private RotateCircleImageView rcv;
........onCreate(){
........
rcv = (RotateCircleImageView) findViewById(R.id.rcv);
}
public void startRotate(View v) {
rcv.startRotate();
}
public void stopRotate(View v) {
rcv.stopRotate();
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android串口開發(fā)之使用JNI實現(xiàn)ANDROID和串口通信詳解
這篇文章主要給大家介紹了關(guān)于Android串口開發(fā)之使用JNI實現(xiàn)ANDROID和串口通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
Android binder 匿名服務(wù)實現(xiàn)雙向通信的解決方案
這篇文章主要介紹了Android binder 匿名服務(wù)實現(xiàn)雙向通信的解決方案,當(dāng)然,這種方案是可行的,只是需要client和server都向servicemanager注冊一個服務(wù),實現(xiàn)起來有點麻煩,不太建議這么做,需要的朋友可以參考下2024-04-04
Android基于Toolbar實現(xiàn)頂部標(biāo)題欄及后退鍵
這篇文章主要介紹了Android基于Toolbar實現(xiàn)頂部標(biāo)題欄及后退鍵,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09

