Android自定義控制項 溫度旋轉按鈕

2021-02-20 龍旋
地址 | https://www.jianshu.com/p/2f7bfe1d73451.介紹

2.思路

初始化一些參數

繪製刻度盤

繪製刻度盤下的圓弧

繪製標題與溫度標識

繪製旋轉按鈕

繪製溫度

處理滑動事件

提供一些接口方法

3.實現初始化一些參數
public class TempControlView extends View {
// 控制項寬 private int width; // 控制項高 private int height; // 刻度盤半徑 private int dialRadius; // 圓弧半徑 private int arcRadius; // 刻度高 private int scaleHeight = dp2px(10); // 刻度盤畫筆 private Paint dialPaint; // 圓弧畫筆 private Paint arcPaint; // 標題畫筆 private Paint titlePaint; // 溫度標識畫筆 private Paint tempFlagPaint; // 旋轉按鈕畫筆 private Paint buttonPaint; // 溫度顯示畫筆 private Paint tempPaint; // 文本提示 private String title = "最高溫度設置"; // 溫度 private int temperature; // 最低溫度 private int minTemp = 15; // 最高溫度 private int maxTemp = 30; // 四格(每格4.5度,共18度)代表溫度1度 private int angleRate = 4; // 按鈕圖片 private Bitmap buttonImage = BitmapFactory.decodeResource(getResources(), R.mipmap.btn_rotate); // 按鈕圖片陰影 private Bitmap buttonImageShadow = BitmapFactory.decodeResource(getResources(), R.mipmap.btn_rotate_shadow); // 抗鋸齒 private PaintFlagsDrawFilter paintFlagsDrawFilter; // 溫度改變監聽 private OnTempChangeListener onTempChangeListener;
// 以下為旋轉按鈕相關
// 當前按鈕旋轉的角度 private float rotateAngle; // 當前的角度 private float currentAngle;
...
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 控制項寬、高 width = height = Math.min(h, w); // 刻度盤半徑 dialRadius = width / 2 - dp2px(20); // 圓弧半徑 arcRadius = dialRadius - dp2px(20); }
...}

繪製刻度盤

以屏幕中心為畫布原點,圓弧角度為270°,繪製未選中與選中狀態的刻度盤。
旋轉方法中多減的2°是後期調整所得,不用在意。

private void drawScale(Canvas canvas) {    canvas.save();    canvas.translate(getWidth() / 2, getHeight() / 2);        canvas.rotate(-133);    dialPaint.setColor(Color.parseColor("#3CB7EA"));    for (int i = 0; i < 60; i++) {        canvas.drawLine(0, -dialRadius, 0, -dialRadius + scaleHeight, dialPaint);        canvas.rotate(4.5f);    }
canvas.rotate(90); dialPaint.setColor(Color.parseColor("#E37364")); for (int i = 0; i < (temperature - minTemp) * angleRate; i++) { canvas.drawLine(0, -dialRadius, 0, -dialRadius + scaleHeight, dialPaint); canvas.rotate(4.5f); } canvas.restore();}

繪製刻度盤下的圓弧
private void drawArc(Canvas canvas) {    canvas.save();    canvas.translate(getWidth() / 2, getHeight() / 2);    canvas.rotate(135 + 2);    RectF rectF = new RectF(-arcRadius, -arcRadius, arcRadius, arcRadius);    canvas.drawArc(rectF, 0, 265, false, arcPaint);    canvas.restore();}

繪製標題與溫度標識
        private void drawText(Canvas canvas) {        canvas.save();
float titleWidth = titlePaint.measureText(title); canvas.drawText(title, (width - titleWidth) / 2, dialRadius * 2 + dp2px(15), titlePaint);
String minTempFlag = minTemp < 10 ? "0" + minTemp : minTemp + ""; float tempFlagWidth = titlePaint.measureText(maxTemp + ""); canvas.rotate(55, width / 2, height / 2); canvas.drawText(minTempFlag, (width - tempFlagWidth) / 2, height + dp2px(5), tempFlagPaint);
canvas.rotate(-105, width / 2, height / 2); canvas.drawText(maxTemp + "", (width - tempFlagWidth) / 2, height + dp2px(5), tempFlagPaint); canvas.restore(); }

繪製旋轉按鈕
private void drawButton(Canvas canvas) {        int buttonWidth = buttonImage.getWidth();    int buttonHeight = buttonImage.getHeight();        int buttonShadowWidth = buttonImageShadow.getWidth();    int buttonShadowHeight = buttonImageShadow.getHeight();
canvas.drawBitmap(buttonImageShadow, (width - buttonShadowWidth) / 2, (height - buttonShadowHeight) / 2, buttonPaint);
Matrix matrix = new Matrix(); matrix.setTranslate(buttonWidth / 2, buttonHeight / 2); matrix.preRotate(45 + rotateAngle); matrix.preTranslate(-buttonWidth / 2, -buttonHeight / 2); matrix.postTranslate((width - buttonWidth) / 2, (height - buttonHeight) / 2);
canvas.setDrawFilter(paintFlagsDrawFilter); canvas.drawBitmap(buttonImage, matrix, buttonPaint);}

繪製溫度
private void drawTemp(Canvas canvas) {    canvas.save();    canvas.translate(getWidth() / 2, getHeight() / 2);
float tempWidth = tempPaint.measureText(temperature + ""); float tempHeight = (tempPaint.ascent() + tempPaint.descent()) / 2; canvas.drawText(temperature + "°", -tempWidth / 2 - dp2px(5), -tempHeight, tempPaint); canvas.restore();}

處理滑動事件
private boolean isDown;private boolean isMove;
@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isDown = true; float downX = event.getX(); float downY = event.getY(); currentAngle = calcAngle(downX, downY); break;
case MotionEvent.ACTION_MOVE: isMove = true; float targetX; float targetY; downX = targetX = event.getX(); downY = targetY = event.getY(); float angle = calcAngle(targetX, targetY);
float angleIncreased = angle - currentAngle;
if (angleIncreased < -270) { angleIncreased = angleIncreased + 360; } else if (angleIncreased > 270) { angleIncreased = angleIncreased - 360; }
IncreaseAngle(angleIncreased); currentAngle = angle; invalidate(); break;
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { if (isDown && isMove) { rotateAngle = (float) ((temperature - minTemp) * angleRate * 4.5); invalidate(); onTempChangeListener.change(temperature); isDown = false; isMove = false; } break; } } return true;}
private float calcAngle(float targetX, float targetY) { float x = targetX - width / 2; float y = targetY - height / 2; double radian;
if (x != 0) { float tan = Math.abs(y / x); if (x > 0) { if (y >= 0) { radian = Math.atan(tan); } else { radian = 2 * Math.PI - Math.atan(tan); } } else { if (y >= 0) { radian = Math.PI - Math.atan(tan); } else { radian = Math.PI + Math.atan(tan); } } } else { if (y > 0) { radian = Math.PI / 2; } else { radian = -Math.PI / 2; } } return (float) ((radian * 180) / Math.PI);}
private void IncreaseAngle(float angle) { rotateAngle += angle; if (rotateAngle < 0) { rotateAngle = 0; } else if (rotateAngle > 270) { rotateAngle = 270; } temperature = (int) (rotateAngle / 4.5) / angleRate + minTemp;}

提供一些接口方法
public void setTemp(int minTemp, int maxTemp, int temp) {    this.minTemp = minTemp;    this.maxTemp = maxTemp;    this.temperature = temp;    this.angleRate = 60 / (maxTemp - minTemp);    rotateAngle = (float) ((temp - minTemp) * angleRate * 4.5);    invalidate();}
public void setOnTempChangeListener(OnTempChangeListener onTempChangeListener) { this.onTempChangeListener = onTempChangeListener;}
public interface OnTempChangeListener { void change(int temp);}

https://github.com/alidili/TempControlView

相關焦點

  • Android實現界面切換時的共享動畫效果
    >不知道大家有沒有注意到,在布局文件中 id 為 image 和 name 的控制項中多了一句 android:transitionName=」」 那麼這個是幹什麼用的呢 ?這個的作用就是和第二個Activity中的 顯示圖片和 文字的控制項關聯起來的,我這邊ImageView控制項的 transitionName=」image」 TextView控制項寫的是 transitionName=」name」,那麼第二個Activity中的布局文件中 對應的 ImageView 控制項和 TextView 也要寫上這麼一句話,這樣 跳轉的時候,第一個界面的控制項才會以移動的方式過渡到第二個界面的對應的位置
  • Android 自定義優雅的BezierSeekBar 之擼碼解析
    0x0 前言某設計網經常會有很多優秀漂亮的互動設計作品,有一天,偶遇這樣的效果,動畫流暢,交互自然,於是埋頭自己解剖其中的元素,做了個開源控制項,十來天有了一百來個star,覺得很受歡迎,今天專門寫這潦草幾筆,分享案發經過,希望對同行有所幫助。0x1 準備效果圖preview效果分析曲線部分
  • Android自定義實現酷炫的提交完成按鈕
    ><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id=
  • Material Design控制項使用(一)
    Material Design的Theme md的主題有:@android:style/Theme.Material (dark version)@android:style/Theme.Material.Light (light version)@android:style/Theme.Material.Light.DarkActionBar
  • Android Material Design系列之Navigation Drawer
    關於Material Design的控制項,從今天這篇開始一個一個的講,希望能夠對大家有所幫助。Material Design系列控制項,我們今天就先從側滑菜單欄開始,側滑菜單欄通過名字我們就知道包含兩部分,一部分是側滑(DrawerLayout),一部分是導航菜單欄(NavigationView)。DrawerLayout包含NavigationView,一設置側滑菜單欄就形成了。
  • kotlin-android-extensions插件也被廢棄了?扶我起來
    ,而是直接調用該控制項在xml中定義的id名稱,就能夠設置其顯示的內容了。在這個函數中首先會嘗試從一個HashMap中獲取傳入的資源id參數所對應的控制項實例緩存,如果還沒有緩存的話,就調用findViewById()函數來查找控制項實例,並寫入HashMap緩存當中。這樣當下次再獲取相同控制項實例的話,就可以直接從HashMap緩存中獲取了。這就是kotlin-android-extensions插件的實現原理,其實還是非常簡單的。
  • 自定義通過PopupWindow實現通用菜單
    會經常用戶到菜單選項提供給用戶選擇,例如選擇圖片,圖庫和相機選擇等一系列場景吧,根據為了以後更加方便使用通過自定義封裝了一個菜單,主要是通過一個列表展示,將菜單項列表傳入設置參數就可以顯示,使用方便簡單只需要幾行代碼就可以,可以顯示在底部,居中和某個控制項的下方。
  • 使用Junit對Android應用進行單元測試
    簡單解析一下這個界面設計,我們使用了LinearLayout,以使得控制項能在垂直方向豎向排列。界面中包括了顯示標題「Unit Testing Sample」的textview,兩個輸入數字的edittext控制項,一個FrameLayout控制項中包含了一個水平的LinearLayout,在這個LinearLayout包含了一個顯示結果的textview以及其提示文字「Result」,注意的是FrameLayout的背景顏色設置為紅色,而LinearLayou設置成了黑色背景。
  • 最新 21 款Android 自定義View及炫酷動畫開源框架,總有一款適合你!
    「等我幹IT發財了,就和你離婚」前言        最近對應用的UI視覺效果突然來了興致,所以找了一些合適開源控制項,這樣更加省時,再此分享給大家,希望能對大家有幫助,此博文介紹的都是UI上面的框架。18.sweet-alert-dialog一個帶動畫效果的自定義對話框樣式項目地址:https://github.com/pedant/sweet-alert-dialog
  • 自定義 EditText 樣式
    自定義EditText 背景一、自定義EditText 圓角矩形背景自定義圓角矩形custom_edittext_background.xml <EditTextandroid:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop
  • Android Material Design系列之FloatingActionButton和Snackbar
    這個系列都是主講的Material Design風格的控制項,所以都是控制項的一些基本使用,也會擴展講一些與這個控制項相關的東西和效果,如果都會了的同學,可以不看這個系列。當然看一下也沒啥,再鞏固一下知識點也挺好的。
  • 手遊<控制項>的思考
    按鈕(Button)官方定義:命令按鈕的作用是對用戶的滑鼠單擊作出反應並觸發相應的事件,在按鈕中既可以顯示正文,也可以顯示位圖。切換按鈕(Toggle)官方定義:切換按鈕可用於從一組選項中進行選擇。特別注意:切換按鈕的狀態區分需要一目了然,操作需要有反饋。多個切換按鈕同時出現時,需要注意接近性原則,控制項內部勾選框和文本的距離要小於控制項和控制項之間的距離。
  • ASP.NET的checkbox控制項學習
    checkbox控制項是類 即classcontrol是控制項類class其中control類包含checkbox類,同樣也包含button類。總結:只要是控制項,control都包含。圖片1像上面的這個圖片展示的html頁面,當滑鼠單擊提交按鈕,也就是觸發onclick事件的時候,上面四個checkbox複選框的checked屬性的狀態,如果選中就顯示在下面的一個
  • Qt自定義Widget之儀錶盤
    上次和大家分享了使用Qml製作的儀錶盤的過程,這次和大家分享下Qt的自定義控制項過程。效果圖如上,有圖有真相啊。程序源碼來自B站上,幾乎沒做任何修改,這裡主要說下自定義控制項的流程。程序源碼:……B站視頻地址:……以前一篇關於一些2D繪圖的文章:……首先Qt已經為我們提供了眾多豐富且易用的基礎控制項,但是很多情況下我們需要實現自己的設計形態,如上面的儀錶盤。這時候就需要自定義控制項了,而且實際使用中還會用到樣式表的。
  • 劉相濤:VBA自製日曆控制項的設計與實現
    ②內置日曆控制項兼容性差,尤其是對於64位系統,日曆組件無法載入。所以,乾脆自己寫一個窗體版的日曆控制項可好?02思路①日曆控制項司空見慣,不是什麼新玩意兒,但大多數都是使用現成的,也是哈,有現成的誰會去費那勁寫呢?!這不是逼上梁山了嘛。②如果你來做,首先會想到什麼?閏年閏月?日曆歌訣?星期?節假日?值的回傳?通用性?
  • Android Q的手勢導航在啟動時將不支持自定義家庭應用
    但是,三個功能並不能完全覆蓋舊的三按鈕欄的所有功能。您也可以長按主屏幕按鈕來召喚Google助手,在以前的測試版中,此功能並未在手勢導航系統中使用。對於Beta 5,Google推出了Google助手的新手勢-從角落滑動。現在,從屏幕底部的任何一個角落拖動滑鼠即可打開Goog​​le助手,並且Google表示「您會注意到「手柄」是我們正在繼續調整的視覺功能。」
  • Android劉海屏適配全方案(華為、小米、Vivo、Oppo)
    gt;= Build.VERSION_CODES.KITKAT) {            getWindow().addFlags(                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);        }    }}頁面的布局很簡單,只包含一個按鈕
  • Android仿全歷史——全沉浸時間軸實現
    android:layout_marginBottom="@dimen/margin_min_2" /></LinearLayout> 這個布局文件除了Tablayout外並沒有什麼好講的,所以我們直接進入第二部分2.Tablayout自定義全古蹟中的tablayout主要就是進行了tab切換後字體的大小、粗細的變化,tab採用了滾動的模式,下劃線Indicator是自定義的一個較短的下劃線
  • VB中的常用控制項
    Click事件:當用戶在一個複選框上單擊滑鼠按鈕時發生。4.5 單 選 按 鈕 單選按鈕控制項與複選框控制項的功能非常相近,複選框表示是否需要某個選項,可以同時選擇多個選項中的一個或多個,即各選項間是不互斥的。單選按鈕則是多選一,只能從多個選項中選擇一個,各選項間的關係是互斥的。