Android自定義View實(shí)現(xiàn)旋轉(zhuǎn)的圓形圖片
自定義View是android開發(fā)的一個(gè)重要技能,用android提供的2/3D繪制相關(guān)類可以實(shí)現(xiàn)非常多炫酷的效果,需要實(shí)打?qū)嵉木幊袒A(chǔ)。
但是自定義View又是我的弱項(xiàng),所以最近都在摸索、練習(xí)自定義View。今天我寫了一個(gè)圓形圖片,同時(shí)不斷勻速旋轉(zhuǎn)的RotateCircleImageView。實(shí)現(xiàn)方法是自己想的,但肯定不是最好的實(shí)現(xiàn)方法。
自定義View分四步。
一:自定義屬性;
二:創(chuàng)建自定義View,在構(gòu)造方法中拿到自定義屬性;
三:重寫onMeasure方法;
四:重寫onDraw方法
先來個(gè)效果圖
先在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è)構(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=順時(shí)針 1=逆時(shí)針 private float rotateSD = 0.8f;//每次旋轉(zhuǎn)的角度--建議范圍0.1f-1,否則會(huì)抖動(dòng) 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);//用這個(gè)類獲得自定義的屬性 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是我的一個(gè)工具類,將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); }
重寫測(cè)量方法:主要是測(cè)量包裹內(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);//分別拿到寬高的大小和測(cè)量模式 int mWidth;//最終寬度 int mHeight;//最終高度 int yy_width = widthSize;//預(yù)測(cè)寬度,先假設(shè)它等于指定大小或填充窗體 if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize;//如果是指定大小或填充窗體(以后直接說成指定大?。?直接設(shè)置最終寬度 } else { yy_width=tempImage.getWidth();//如果是包裹內(nèi)容,則預(yù)測(cè)寬度等于圖片寬度 mWidth = yy_width + getPaddingLeft() + getPaddingRight();//最終寬度等于預(yù)測(cè)寬度加 左右Padding寬度 } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize;//同上 } else { mHeight = getPaddingTop() + getPaddingBottom() + yy_width;//最終高度等于預(yù)測(cè)寬度加 上下Padding寬度 //目的是讓控件的寬高相等,但Padding是可以由用戶自由指定的,所以再加上padding } if (tempImage.getHeight() < tempImage.getWidth()) { //這里用Bitmap類提供的縮放方法把圖片縮放成指定大小,如果圖片高度比寬度小,則強(qiáng)制拉伸 image = Bitmap.createScaledBitmap(tempImage, yy_width - bkWidth, yy_width - bkWidth, false); } else { //這里用Bitmap類提供的縮放方法把圖片縮放成指定大小(寬度等于預(yù)測(cè)的寬度,高度按比例縮放) //該方法根據(jù)參數(shù)的寬高強(qiáng)制縮放圖片,所以這里根據(jù)寬度算出縮放后的高度 image = Bitmap.createScaledBitmap(tempImage, yy_width - bkWidth,(int) (tempImage.getHeight() / (((float) tempImage.getWidth()) / yy_width) - bkWidth), false); } setMeasuredDimension(mWidth, mHeight);//設(shè)置View的寬高,測(cè)量結(jié)束 }
假如寬度是指定大小,我希望高度根據(jù)這個(gè)大小按比例縮放,那么我需要拿到圖片原始大小,所以需要一個(gè)tempImage,為什么寫一個(gè)臨時(shí)的Bitmap?因?yàn)槲覝y(cè)試的時(shí)候發(fā)現(xiàn) 假如我用這個(gè)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毫秒后啟動(dòng)子線程 } }
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);//忘了是啥....反正效果好點(diǎn) bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);//創(chuàng)建一個(gè)指定寬高的空白bitmap isCreateBitmap = true; canvas = new Canvas(bitmap);//用那個(gè)空白bitmap創(chuàng)建一個(gè)畫布 canvas.drawCircle(width / 2, width / 2, width / 2, bitmapPaint);//在畫布上畫個(gè)圓 pdf = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);//創(chuàng)建一個(gè)混合模式為保留后者相交的部分 } bitmapPaint.setXfermode(pdf);//設(shè)置混合模式 if (rotate_fx==0) { canvas.rotate(rotate, width / 2, width / 2);//順時(shí)針 } else {//旋轉(zhuǎn)畫布:意思是下一次繪制的內(nèi)容會(huì)被旋轉(zhuǎn)這么多個(gè)角度 canvas.rotate(-rotate, width / 2, width / 2);//逆時(shí)針 } canvas.drawBitmap(image, 0, 0, bitmapPaint);//繪制圖片,(圖片會(huì)被旋轉(zhuǎn)) bitmapPaint.setXfermode(null); return bitmap;//這個(gè)bitmap在畫布中被旋轉(zhuǎn),畫圓,返回后就是一個(gè)圓形的bitmap } private Handler handler = new Handler(); private Runnable runnable = new Runnable() { @Override public void run() { invalidate();//刷新界面 } };
在第一次執(zhí)行onDraw方法的時(shí)候得到的是一個(gè)旋轉(zhuǎn)了0.8度的bitmap,然后16毫秒后啟動(dòng)子線程刷新,再次執(zhí)行onDraw,得到一個(gè)再次旋轉(zhuǎn)0.8度的bitmap,以此類推,所以不斷旋轉(zhuǎn)。想要轉(zhuǎn)的快一點(diǎn)就把每次旋轉(zhuǎn)的角度調(diào)大一點(diǎn),但是不能太大,否則效果很不好。一卡一卡的。這樣就完成了這個(gè)自定義view,非常簡單,但是我卻折騰了好久,主要還是測(cè)量的時(shí)候不夠細(xì)心。實(shí)現(xiàn)方法都是自己整出來的,如果有更好的實(shí)現(xiàn)方法歡迎告知。
最后再暴露兩個(gè)方法給外部
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中拿到控件,重寫兩個(gè)按鈕的點(diǎn)擊事件方法:
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)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android裁剪圖片為圓形圖片的實(shí)現(xiàn)原理與代碼
- Android中Glide加載圓形圖片和圓角圖片實(shí)例代碼
- Android實(shí)現(xiàn)圓形圖片的兩種方式
- 詳解Android中Glide與CircleImageView加載圓形圖片的問題
- 分享一個(gè)Android設(shè)置圓形圖片的特別方法
- Android編程繪制圓形圖片的方法
- android繪制圓形圖片的兩種方式示例
- Android實(shí)現(xiàn)圓形圖片或者圓角圖片
- Android使用自定義ImageView實(shí)現(xiàn)圓形圖片效果
- Android實(shí)現(xiàn)圓形圖片效果
相關(guān)文章
Android實(shí)戰(zhàn)項(xiàng)目之實(shí)現(xiàn)一個(gè)簡單計(jì)算器
隨著移動(dòng)互聯(lián)網(wǎng)的普及,手機(jī)應(yīng)用程序已經(jīng)成為人們生活中不可或缺的一部分,計(jì)算器是一類被廣泛使用的應(yīng)用程序之一,這篇文章主要給大家介紹了關(guān)于Android實(shí)戰(zhàn)項(xiàng)目之實(shí)現(xiàn)一個(gè)簡單計(jì)算器的相關(guān)資料,需要的朋友可以參考下2023-10-10詳解Android studio如何導(dǎo)入jar包方法
這篇內(nèi)容主要給大家詳細(xì)說明了如何導(dǎo)入jar包,以及Android studio遇到的各種問題和解決辦法。2017-12-12Android為應(yīng)用添加數(shù)字角標(biāo)的簡單實(shí)現(xiàn)
應(yīng)用的角標(biāo)是用來標(biāo)記有多少條提醒沒讀,本篇文章主要介紹了Android為應(yīng)用添加角標(biāo)的簡單實(shí)現(xiàn),有興趣的可以了解一下。2017-04-04Android AndBase框架實(shí)現(xiàn)多功能標(biāo)題欄(一)
這篇文章主要整理了Android AndBase框架學(xué)習(xí)筆記,本文主要使用AndBase實(shí)現(xiàn)多功能標(biāo)題欄,感興趣的小伙伴們可以參考一下2016-03-03ListView的Adapter使用 之 初學(xué)ArrayAdapter String
ListView是Android中經(jīng)常會(huì)使用的東西,綁定數(shù)據(jù)對(duì)于初學(xué)者來說,尤其是剛接觸編程的人來說,往往會(huì)覺得很難理解,我上大二的時(shí)候?qū)W的java,但是基本上相當(dāng)于沒有學(xué),什么都沒寫過,真正接觸編程就是開始上手學(xué)android,把這些記錄下來,自己可以回頭看下,也可以讓新手更好的理解2013-06-06android實(shí)現(xiàn)簡單的畫畫板實(shí)例代碼
畫畫板實(shí)現(xiàn)起來其實(shí)很簡單,我們只需要利用android給我們提供的Canvas類來操作就可以實(shí)現(xiàn)簡單的畫畫功能2014-01-01Android之Notification的多種用法實(shí)例
本篇文章主要介紹了Android之Notification的多種用法實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12