• <rp id="vcwyv"></rp>

      <b id="vcwyv"></b>
      <tt id="vcwyv"></tt>

    1. 優雅的氣球選擇器 BalloonPicker

      [復制鏈接]
      來自: fairytale110 分類: Android精品源碼 上傳時間: 2019-10-24 16:48:47
      Tag:

      項目介紹:

      BalloonPicker

      Download demo apk

      控件拆分:

      元素 拆分 效果
      基線 已選擇、未選擇 根據觸摸塊改變長度
      觸摸塊 觸摸動畫、內外圓 觸摸時,內外圓按各自約束勻速放大;結束時,內外圓按各自約束勻速縮小
      氣球 縮放、位移 觸摸前后縮放,移動時,勻速移動,中心旋轉
      文本 描述、值回調 普通文本、觸摸顯示回調值

      繪制流程拆分

      繪制基線和觸摸塊

      分別繪制選中和未選中的基線
      然后繪制觸摸塊外圓和內圓 
      

      為觸摸塊添加動畫

      觸摸塊樣式動畫

      以外圓圓心為中心,達到半徑100像素范圍內的觸摸點,均可觸發內圓縮放動畫。

      1、默認觸摸狀態下,動畫以默認半徑開始勻速遞增,同時刷新視圖,直到最大內圓半徑為止。

      2、放大過程中,如果用戶手指離開屏幕,觸發MOVE_UP事件,則停止已有放大動畫,轉而執行縮小內圓半徑的動畫,直到達到最小內圓半徑為止。

      3、如果縮小過程中用戶再次觸摸此區域,則重復執行過程1,以此達到跟隨交互效果。

      同理,外圓的縮放規則遵循上述規則

      為了讓動畫效果更加平順,并且不浪費太多時間在縮放過程中,我們將在縮放開始前結束已在執行中的動畫,并重新計算剩余縮放過程需要的時間差,用于當前縮放過程。

      縮放動畫時間差計算公式:

      實際動畫持續時間 = 動畫持續時間 * 剩余縮(放)距離/總縮(放)距離

      val remainingTime: Long = when {
                      this.increase -> (duration* (thumbInnerCircleRadiusMax - thumbInnerCircleRadiusTemp)/(thumbInnerCircleRadiusMax - thumbInnerCircleRadiusDefault)).toLong()
                      else -> (duration* (thumbInnerCircleRadiusTemp - thumbInnerCircleRadiusDefault)/(thumbInnerCircleRadiusMax - thumbInnerCircleRadiusDefault)).toLong()
      } 
      

      觸摸塊位移

      當觸發MOVE_MOVE時,根據觸摸坐標x差值,更新觸摸塊的x坐標。同時,計算出此時選擇器的value,更新兩側基線狀態,并執行回調。

      val x = pointOfThumbTemp.x + event.x - pointOfTouchDown.x
      
      pointOfThumb = PointF( if (x > xOfTrackLayerEnd) xOfTrackLayerEnd  else ( if (x < xOfTrackLayerStart) xOfTrackLayerStart else x), pointOfThumbTemp.y)
      
      selectedValue = (this.minValue.toFloat() + (this.maxValue.toFloat() - this.minValue.toFloat()) * (pointOfThumb.x - xOfTrackLayerStart) / (widthOfView - 2 * xOfTrackLayerStart)).toLong()
      
      postInvalidate()
      
      listener?.callBack(selectedValue)
      

      氣球動畫

      通過拆分:
      1、ACTION_MOVE,當觸摸坐標發生位移,氣球旋轉對應角度以保持風力阻擋的慣性。氣球中心點到基線的垂線,與氣球中心點和觸點中心點 的直線的夾角,即為當前狀態下的旋轉角度
      //分析圖

       override fun locationOfThumb(pointF: PointF) {
              pointThumb.set(pointF.x, height.toFloat() - trackLayer?.getPadding()!!)
              val b = pointF.x - trackLayer?.getPadding()!!
              val angleRoTan = -atan(b/distanceVerticalBetweenBalloonAndTrackLayer) / PI  * 180
              L("angleRoTan $angleRoTan")
              balloon?.rotation = if (angleRoTan.toFloat() > 0F ) 0F else angleRoTan.toFloat()
              postInvalidate()
       }
      

      此時氣球能跟著“線”被“手”帶”動“了,但是氣球還沒有移動,“線”也沒有無限長,行,我們先讓氣球移動起來。

      這里需要設定一個閥值,即“線”的長度,當超過這個閥值,則氣球將被“拽著”移動。

       private fun moveBalloon(){
          val ptb2 = (pointThumb.x - centerOfBalloon.x).toDouble().pow(2.0)
          val c =  sqrt (ptb2 + centerOfBalloon.y * centerOfBalloon.y)
          ...
       }
      

      直接計算“線”長來判斷,但是這樣需要繁瑣的符號運算,這里我們可以直接找個參考數據,簡化邏輯過程:

       private fun moveBalloon(){
          val b = pointThumb.x - centerOfBalloon.x
          ...
       }
      

      通過觸點與氣球中心點的垂直距離的變化來判斷是否需要進行“拽著”移動:

          override fun locationOfThumb(pointF: PointF) {
              pointThumb.set(pointF.x, height.toFloat() - trackLayer?.getPadding()!!)
              moveBalloon()
          }
      
          private fun moveBalloon(){
              val b = pointThumb.x - centerOfBalloon.x
              val ins = abs(b) - (height - pointThumb.y)
              if (b != 0F && ins > 0){
      
                  val xOfBalloon = centerOfBalloon.x.toInt() - balloon?.layoutParams!!.width / 2 + if (b > 0)  ins.toInt() else - ins.toInt()
      
                  balloon?.layout( xOfBalloon, balloon?.y!!.toInt(), xOfBalloon + balloon?.layoutParams!!.width, measuredHeight - trackLayer?.layoutParams!!.height)
      
                  centerOfBalloon.set(balloon?.x!! + balloon?.layoutParams!!.width / 2F,  balloon?.y!! + balloon?.layoutParams!!.height / 2)
      
              } else {
                  //TODO moveBalloonWithAnim
              }
      
              val angleRoTan = -atan(b/distanceVerticalBetweenBalloonAndTrackLayer) / PI  * 180
              L("angleRoTan $angleRoTan")
              balloon?.rotation = angleRoTan.toFloat()
              postInvalidate()
          }
      

      此時氣球已經可以被拽著走了,為了讓效果更加逼真,在閥值內,我們通過動畫來緩慢移動,

      2、同時以勻速向新的圓點移動,直到氣球中心x與觸摸點x重合。
      通過監聽將TrackLayer的touch數據傳遞給pickerView,改造了統一的接口:

      interface TrackLayerListener {
         fun layerTouchedDown()
         fun layerTouchedUp()
         fun layerTouchedMoving(value : Long, pointAtLayer : PointF)
      }
      

      在 layerTouchedMoving() 中處理氣球的移動邏輯.

      當球心與圓點距離小于閥值時,中斷氣球動畫,直接布局氣球在picker中的位置;否則,執行新的氣球動畫:

       override fun layerTouchedMoving(value: Long, pointAtLayer: PointF) {
              pointThumb.set(pointAtLayer.x, height.toFloat() - trackLayer?.getPadding()!!)
              val b = pointThumb.x.toInt() - centerOfBalloon.x.toInt()
              if (abs(b) > distanceVerticalBetweenBalloonAndTrackLayer.toInt()){
                  initAnimation(ValueAnimator.ofInt(centerOfBalloon.x.toInt(), pointThumb.x.toInt()))
      
                  val xOfBalloon = (centerOfBalloon.x - balloon?.layoutParams!!.width / 2 + if (b > 0)  b-distanceVerticalBetweenBalloonAndTrackLayer else b + distanceVerticalBetweenBalloonAndTrackLayer).toInt()
      
                  balloon?.layout( xOfBalloon , balloon?.y!!.toInt(), xOfBalloon + balloon?.layoutParams!!.width, balloon?.y!!.toInt() +balloon?.layoutParams!!.height )
      
                  centerOfBalloon.set(xOfBalloon + balloon?.layoutParams!!.width / 2F,  balloon?.y!! + balloon?.layoutParams!!.height / 2)
      
                  rotateBalloon()
              }
              moveBalloon()
          }
      


      效果還可以,接下來就需要根據picker的取值來動態縮放氣球,同時維持住氣球的底部位置不變
      本計劃直接調用scale API, 奈何privot也需要動態控制,不然不能維持氣球底部垂直位置不變。

       override fun layerTouchedMoving(value: Long, pointAtLayer: PointF) {
                  //...
                  val valueAtBalloon =trackLayer?.minValue()!! +  (trackLayer?.maxValue()!! - trackLayer?.minValue()!!) *  centerOfBalloon.x/measuredWidth
      
                  val disScaleHeight = balloonHeightDefault * (valueAtBalloon - trackLayer?.minValue()!!) / (trackLayer?.maxValue()!! - trackLayer?.minValue()!!)
      
                  val disScaleWidth = balloonWidthDefault/2 * (valueAtBalloon - trackLayer?.minValue()!!) / (trackLayer?.maxValue()!! - trackLayer?.minValue()!!)
      
                  //...
                  balloon?.layout( xOfBalloon  , balloonDefaultY.toInt() - disScaleHeight.toInt(), xOfBalloon + balloonWidthDefault.toInt() + disScaleWidth.toInt() * 2, (balloonDefaultY + balloonHeightDefault).toInt())
                  centerOfBalloon.set(xOfBalloon + disScaleWidth + balloonWidthDefault / 2F,  balloonDefaultY + balloonHeightDefault/2 - disScaleHeight/2 )
                  //...
              }
              //...
       }
      

      給氣球打上輔助線,我們來看下效果:

      4、氣球顯示隱藏
      氣球能動能縮放了,接下來給氣球加入出入動畫,

      默認情況下,不展示氣球,當ACTION_DOWN 觸發,氣球沖圓點漸顯 & 放大 & 移動 到初始位置;

      當ACTION_UP 觸發,氣球從當前位置 淡出 & 縮小 & 移動 到圓點位置

      override fun layerTouchedDown() {
              balloon?.startAnimation(BalloonAnimSet.create(true, 0F, 0F, pointThumb.y - balloon?.y!!, 0F, context , listenerEnter))
      }
      
      override fun layerTouchedUp() {
              balloon?.visibility = View.INVISIBLE
              pointThumb.set(trackLayer?.centerPoint()!!.x, height.toFloat() - trackLayer?.getPadding()!!)
      
              initAnimation(ValueAnimator.ofInt(centerOfBalloon.x.toInt(), pointThumb.x.toInt()))
      
              moveBalloon()
              balloon?.startAnimation(BalloonAnimSet.create(false, 0F, 0F, 0F, pointThumb.y - balloon?.y!!, context , listenerExit))
      }
      

      來看看效果吧:

      開始使用

      修飾一下,拋出必要的樣式設置方法,最終效果完成:
      //使用方法

      Add it in your root build.gradle at the end of repositories:

      allprojects {
          repositories {
              ...
              maven { url 'https://jitpack.io' }
          }
      }
      

      Step 2. Add the dependency

      dependencies {
              implementation 'com.github.fairytale110:BalloonPicker:1.0.1'
      

      Then, Drop it to XML layout or new it

      <tech.nicesky.balloonpicker.BalloonPickerView
              android:id="@+id/balloon_picker"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
       />
      

      Finally, custom it's style as you want

      fun load(){
           balloon_picker.layerValues(10, 50, 5)
      
           balloon_picker.defaultValue(30)
           balloon_picker.setColorFoThumb("#FFFFFF".toColorInt(), "#512DA8".toColorInt())
           balloon_picker.setColorForLayer("#512DA8".toColorInt(), "#BDBDBD".toColorInt())
           balloon_picker.setColorForBalloon("#512DA8".toColorInt())
           balloon_picker.setColorForBalloonValue("#FFFFFF".toColorInt())
           balloon_picker.colorOfDesc = "#000000".toColorInt()
           balloon_picker.colorOfValue = "#000000".toColorInt()
           balloon_picker.desc = "Quantity"
           balloon_picker.valueListener = object : BalloonPickerListener{
                     override fun changed(value: Long) {
                            Log.w("MainActivity","value: $value")
                     }
           }
           // val valueSelected = balloon_picker.getValue()
       }
      

      當然,這個控件還有很大的優化空間,歡迎諸位一起探討。歡迎star

      GitHub: https://github.com/fairytale110/BalloonPicker

      相關源碼推薦:

      我來說兩句
      所有評論(54)
      打個醬油的 2019-10-24 18:46:24
      感謝分享,安卓巴士有你更精彩:lol
      回復
      ff12345 2019-10-24 18:46:44
      感謝分享,樓主V5~
      回復
      bug是啥 2019-10-24 18:50:54
      幫幫頂頂!!
      回復
      無限釋囚 2019-10-24 19:03:44
      每次我都積極回帖的,想要安幣~
      回復
      仲夏炎涼。 2019-10-24 19:05:04
      不錯不錯,樓主辛苦了。。。
      回復
      subsoil 2019-10-24 19:10:14
      樓主威武,以后多發干貨,多辦活動~!
      回復
      apkbus熱心網友 2019-10-25 10:38:47
      我只是路過打醬油的。
      回復
      提取碼:  下載次數:15 狀態:已購或VIP 售價:10(原價:10)金錢 下載權限:初級碼農 
      2340 1 15
      代碼貢獻英雄榜
      用戶名 下載數
      聯系我們
      首頁/微信公眾賬號投稿
      帖子代碼編輯/版權問題
      QQ:435399051,1294855032
      如何獲得代碼達人稱號?
      如何成為簽約作者?
      領先的中文移動開發者社區
      18620764416
      7*24全天服務
      意見反饋:1294855032@qq.com

      掃一掃關注我們

      Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

      夫妻性姿势真人示范 - 视频 - 在线观看 - 影视资讯 - 唯爱网