国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

教你五分鐘實現Android超漂亮的刻度輪播控件實例教程

2019-10-21 21:39:17
字體:
來源:轉載
供稿:網友

前言

最近一直在做音視頻的工作,已經有大半年沒有寫應用層的東西了,生怕越來越生疏。正好前段時間接了個外包項目,才得以回顧一下。項目中有一個控件挺簡潔漂亮的,而且用到的技術也比較基礎,比較適合新手學習,所以單獨開源出來,希望能對初學者有所幫助。

Android,刻度,輪播控件
截圖

Android,刻度,輪播控件

截屏

一、自定義View的常用方法

相信每個Android程序員都知道,我們每天的開發工作當中都在不停地跟View打交道,Android中的任何一個布局、任何一個控件其實都是直接或間接繼承自View的,如TextView、Button、ImageView、ListView等。

一些接觸Android不久的朋友對自定義View都有一絲畏懼感,總感覺這是一個比較高級的技術,但其實自定義View并不復雜,有時候只需要簡單幾行代碼就可以完成了。

說到自定義View,總繞不開下面幾個方法

1. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)

  初始化View時,用于測量大小,并對View的大小進行控制,比如可以控制View的寬高比例。

2. override fun onDraw(canvas: Canvas)

  View的繪制回調,所有的畫筆、畫布操作都在這里。切勿在此方法進行耗時操作,能在外部計算的都在外部計算,并且盡量不要在這里初始化變量。因為正常情況下這個方法會以60fps的速度進行回調,如果有耗時操作,將會卡頓,如果初始化大量對象,則會消耗大量內存。總之,跟畫布無關的操作都不要寫在這里。

3. invalidate()

  用于通知View進行重繪,也就是重新調用onDraw,當我們界面屬性發生變化時,就可以調用該方法來進行重繪,而不是調用onDraw,這個方法非常常用。

4. override fun onTouchEvent(event: MotionEvent): Boolean

  相信大家都知道,這個是觸摸事件回調。在這里可以處理一些手勢操作。

二、自定義一個刻度控件RulerView

  由于代碼比較多,而且源碼里面的注釋也比較詳細,所以這里只挑重點的幾個方法講解一下。如果有問題,或者錯誤,歡迎在評論區留言。

  觀察本文開始的視頻,我們可以發現,該控件雖然看起來挺簡潔,但是需要控制的部分卻不少,光刻度就有三種類型,還有一些文字。

普通刻度,寬度比較短,顏色比較淺,不帶文字。
整10刻度,寬度比較長,顏色相較普通刻度深一點,并且帶有文字。
游標刻度,寬度在三類刻度里面是最長的,顏色高亮,并且也帶有文字。
標簽文字,用于描述該刻度的用途。

  以上都是需要我們用畫筆來繪制的,所以我們定義了以下幾個畫筆,為了避免在onDraw中頻繁更改畫筆屬性,這里又對文字和刻度定義了單獨的畫筆,目的是避免任何畫筆屬性的改變和在onDraw中改變屬性導致繪制過于耗時,更重要的是來回更改畫筆的屬性過于復雜,不便于操作和問題排查。

scalePaint: Paint //刻度畫筆
scalePointerPaint: Paint //整10刻度文字畫筆
scalePointerTextPaint: Paint //整10刻度文字畫筆
cursorPaint: Paint //游標畫筆
cursorTextPaint: Paint //游標文字畫筆
cursorLabelPaint: Paint //標簽文字畫筆

1、從xml設置的屬性初始化參數

  除了基礎的畫筆對象,還需要一些畫筆必要的屬性,比如我們繪制一個刻度,需要知道刻度位置、大小和間距。所以圍繞這些,又定義了一系列屬性。這些屬性可以由xml定義時提供,由此引出View的另一個重要用法。

  這個用法比較固定,都是這個套路。其中需要注意的是,類似于R.styleable.app_scaleWidth這種id是在values/attrs.xml中定義的,app代表命名空間,可以自定義,scaleWidth就是屬性id,跟layout_width這些是一樣的。我們在一個命名空間中定義了一個屬性id后,就可以像使用layout_width和layout_height那樣從xml中向View傳遞屬性了。此時在View的構造方法中可以直接獲取這些屬性值,代碼如下。

/** * 從xml設置的屬性初始化參數 */private fun resolveAttribute(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) { scaleStrokeWidth = dpToPx(context, 1f) / 2f scaleWidth = 50 scalePointerWidth = (scaleWidth * 1.5).toInt() cursorWidth = (scaleWidth * 3.333).toInt() scaleHeight = 5 cursorColor = context.resources.getColor(R.color.red) scaleColor = context.resources.getColor(R.color.grey_888) scalePointerColor = context.resources.getColor(R.color.grey_800) val a = context.theme.obtainStyledAttributes(attrs, R.styleable.app, defStyleAttr, defStyleRes) for (i in 0 until a.indexCount) { val attr = a.getIndex(i) if (attr == R.styleable.app_scaleWidth) {  scaleWidth = a.getDimensionPixelOffset(attr, 50)  scalePointerWidth = (scaleWidth * 1.5).toInt()  cursorWidth = (scaleWidth * 3.333).toInt() } else if (attr == R.styleable.app_scaleHeight) {  scaleHeight = a.getDimensionPixelOffset(attr, 5) } else if (attr == R.styleable.app_cursorColor) {  cursorColor = a.getColor(attr, context.resources.getColor(R.color.red)) } else if (attr == R.styleable.app_scaleColor) {  scaleColor = a.getColor(attr, context.resources.getColor(R.color.grey_888)) } else if (attr == R.styleable.app_scalePointerColor) {  scalePointerColor = a.getColor(attr, context.resources.getColor(R.color.grey_800)) } } cursorTextOffsetLeft = dpToPx(context, 32f) a.recycle()}

2、繪制View

  本文并沒有使用View提供的scrollTo和scrollBy來控制滾動,而是重新定義一個x,y屬性來記錄滾動位置,通過這個屬性繪制相應的位置,來實現滾動效果。這樣操作可以通過指定繪制區域(屏幕外的內容不繪制,感興趣的同學可以去嘗試實現)來解決性能問題。

  drawScale通過遍歷items來繪制每一個元素,包括刻度和對應的文字,都是比較基本的操作。需要注意的是canvas.drawText默認情況下的x,y是指文字的左下角位置。

private fun drawScale(canvas: Canvas) { for (i in 0 until items.size) {//根據給定的item信息繪制刻度 val top = offsetHeight + mCurrentOrigin.y.toInt() + i * scaleHeight + scaleHeight / 2 if (0 == i % 10) {//繪制整10刻度  canvas.drawRect(RectF(pointerScaleLeft.toFloat(), top - scaleStrokeWidth,   pointerScaleLeft.toFloat() + scalePointerWidth,   top + scaleStrokeWidth),   scalePointerPaint)  if (Math.abs(getSelectedItem() - i) > 1) {//整10刻度有文字,所以需要計算文字位置,并繪制文字  val text = items[i].toString()  val size = measureTextSize(scalePointerTextPaint, text)  canvas.drawText(text, pointerScaleLeft - size[0] * 1.3f, top + size[1] / 2, scalePointerTextPaint)  } } else {//繪制普通刻度  canvas.drawRect(RectF(scaleLeft.toFloat(), top - scaleStrokeWidth,   scaleLeft.toFloat() + scaleWidth,   top + scaleStrokeWidth),   scalePaint) } }}/** * 繪制游標,這里也需要計算文字位置,包括item文字和標簽文字 */private fun drawCursor(canvas: Canvas) { val left = scaleLeft + scaleWidth - cursorWidth val top = measuredHeight / 2f canvas.drawRect(RectF(left.toFloat(), top.toInt() - scaleStrokeWidth,  left.toFloat() + cursorWidth, top.toInt() + scaleStrokeWidth),  cursorPaint) val text = items[getSelectedItem()].toString() val textSize = measureTextSize(cursorTextPaint, text) val labelSize = measureTextSize(cursorLabelPaint, label) val labelLeft = left - cursorTextOffsetLeft - labelSize[0] val textOffset = (textSize[0] - labelSize[0]) / 2f canvas.drawText(text, left - cursorTextOffsetLeft - textSize[0] + textOffset, top + textSize[1] / 2, cursorTextPaint) canvas.drawText(label, labelLeft, top + textSize[1] + labelSize[1], cursorLabelPaint)}

3、支持滾動

  Android的手勢滾動操作比較簡單,不需要自己去實現各種邏輯控制,而是通過系統提供的Scroller來計算滾動位置。

  首先我們需要一個GestureDetectorCompat和OverScroller,前者用于手勢監聽,后者通過MotionEvent來計算滾動位置。

1.mGestureDetector: GestureDetectorCompat

2.scroller: OverScroller

private fun init() { mGestureDetector = GestureDetectorCompat(context, onGestureListener) scroller = OverScroller(context)}

  構造一個GestureDetectorCompat對象,需要先提供一個OnGestureListener,用來監聽onScroll和onFling事件。其實就是MotionEvent經過GestureDetectorCompat處理之后,就變成了可以直接使用的滾動和慣性滾動事件,然后通過這兩個回調通知我們。

  在onScroll中,我們通過橫向和縱向滾動距離來計算滾動方向,如果橫向滾動距離大于縱向滾動距離,我們則可以認為是橫向滾動,反之則是縱向滾動。本文只需要縱向滾動。

  拿到滾動方向之后,我們就可以對滾動位置x,y進行累加,記錄每一次滑動之后的新的位置。最后通過postInvalidateOnAnimation或invalidate來通知重新繪制,onDraw根據新的x,y繪制對應位置的畫面,來實現滑動。

  雖然通過onScroll已經實現了View的滑動,但只是實現跟隨手指運動,還沒有實現“拋”的動作。在現實世界中,運動是有慣性的,如果只實現onScroll,一切都顯得很生硬。那么如何實現慣性運動呢,我們自己計算?想想都可怕,這么多運動函數,相信不是一般人能應付的來的。幸運的是,這個計算我們可以交給GestureDetectorCompat的onFling。

  onFling有四個參數,前兩個是MotionEvent,分別代表前后兩個觸摸事件。velocityX: Float代表X軸滾動速率,velocityY: Float代表Y軸滾動速率,我們不需要關心這兩個值如何,直接交給scroller處理即可。

  這里也許有人要問了,我們的手指離開屏幕之后便不再產生事件,View是如何實現持續滑動的呢。再回頭看一下onFling回調也確實如此,onFling只會根據手指離開屏幕前兩個MotionEvent來計算速率,之后就再也沒有回調,所以scroller.fling也僅僅是調用了一次,并不能持續滾動。那我們如何實現持續的慣性滾動呢?

  要實現持續的慣性滾動,就得依賴于override fun computeScroll(),該方法由draw過程中調用,我們可以通過invalidate->onDraw->computeScroll->invalidate這樣一個循環來控制慣性滾動,直至慣性滾動停止,具體實現可以參考文章最后的源碼。

/** * 手勢監聽 */private val onGestureListener = object : GestureDetector.SimpleOnGestureListener() { /**  * 手指按下回調,這里將狀態標記為非滾動狀態  */ override fun onDown(e: MotionEvent): Boolean {  parent.requestDisallowInterceptTouchEvent(true)  mCurrentScrollDirection = Direction.NONE  return true } /**  * 手指拖動回調  */ override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {  //如果當前正在滾動,則停止滾動  scroller.forceFinished(true)//   Log.e(RulerView::class.java, "onScroll: ${mCurrentOrigin.y}, $distanceY")  if (Direction.NONE == mCurrentScrollDirection) {//判斷滾動方向,這里只有垂直一個方向   mCurrentScrollDirection = if (Math.abs(distanceX) < Math.abs(distanceY)) {    Direction.VERTICAL   } else {    Direction.NONE   }  }  // Calculate the new origin after scroll.  when (mCurrentScrollDirection) {   Direction.VERTICAL -> {//計算手指拖動距離,并記錄新的坐標重繪界面    mCurrentOrigin.y -= distanceY    checkOriginY()    ViewCompat.postInvalidateOnAnimation(this@RulerView)   }  }  return true } /**  * 慣性滾動回調  */ override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {  scroller.forceFinished(true)  mCurrentFlingDirection = mCurrentScrollDirection  when (mCurrentFlingDirection) {   Direction.VERTICAL -> scroller.fling(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(),     0, velocityY.toInt(), Integer.MIN_VALUE,     Integer.MAX_VALUE, Integer.MIN_VALUE, 0)  }  ViewCompat.postInvalidateOnAnimation(this@RulerView)  return true }}

  至此自定義View的繪制和事件兩個重要部分都講完了。喜歡的話記得點贊、評論和關注,您的關注是我的鼓勵。文章最后貼出相關源碼,歡迎查閱學習。如果有問題,或者錯誤,歡迎在評論區留言。

完整代碼

  RulerView.kt

class RulerView : View { private enum class Direction {  NONE, VERTICAL } private var label: String = "LABEL" private var items: List<*> = ItemCreator.range(0, 60) //游標顏色 private var cursorColor = 0 //刻度顏色 private var scaleColor = 0 //整10刻度顏色 private var scalePointerColor = 0 //可滾動高度 private var scrollHeight = 0f //刻度寬度 private var scaleWidth = 0 //整10刻度寬度 private var scalePointerWidth = 0 //游標寬度 private var cursorWidth = 0 //刻度高度+刻度間距 private var scaleHeight = 0 //刻度高度 private var scaleStrokeWidth = 0f //刻度畫筆 private lateinit var scalePaint: Paint //整10刻度畫筆 private lateinit var scalePointerPaint: Paint //整10刻度文字畫筆 private lateinit var scalePointerTextPaint: Paint //游標畫筆 private lateinit var cursorPaint: Paint //游標文字畫筆 private lateinit var cursorTextPaint: Paint //標簽文字畫筆 private lateinit var cursorLabelPaint: Paint //刻度間距 private var offsetHeight = 0 //刻度與文字的間距 private var cursorTextOffsetLeft = 0 //刻度距離View左邊的距離 private var scaleLeft = 0 //整10刻度距離View左邊的距離 private var pointerScaleLeft = 0 //滾動控制器 private lateinit var scroller: OverScroller private var maxFlingVelocity = 0 private var minFlingVelocity = 0 private var touchSlop = 0 //當前滾動方向 private var mCurrentScrollDirection = Direction.NONE //當前慣性滾動方向 private var mCurrentFlingDirection = Direction.NONE //當前滾動x,y private val mCurrentOrigin = PointF(0f, 0f) //手勢支持 private lateinit var mGestureDetector: GestureDetectorCompat constructor(context: Context) : super(context) {  resolveAttribute(context, null, 0, 0)  init() } constructor(context: Context, attrs: AttributeSet?)   : super(context, attrs) {  resolveAttribute(context, attrs, 0, 0)  init() } constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int)   : super(context, attrs, defStyleAttr) {  resolveAttribute(context, attrs, defStyleAttr, 0)  init() } /**  * 從xml屬性初始化參數  */ private fun resolveAttribute(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) {  scaleStrokeWidth = dpToPx(context, 1f) / 2f  scaleWidth = 50  scalePointerWidth = (scaleWidth * 1.5).toInt()  cursorWidth = (scaleWidth * 3.333).toInt()  scaleHeight = 5  cursorColor = context.resources.getColor(R.color.red)  scaleColor = context.resources.getColor(R.color.grey_888)  scalePointerColor = context.resources.getColor(R.color.grey_800)  val a = context.theme.obtainStyledAttributes(attrs, R.styleable.app, defStyleAttr, defStyleRes)  for (i in 0 until a.indexCount) {   val attr = a.getIndex(i)   if (attr == R.styleable.app_scaleWidth) {    scaleWidth = a.getDimensionPixelOffset(attr, 50)    scalePointerWidth = (scaleWidth * 1.5).toInt()    cursorWidth = (scaleWidth * 3.333).toInt()   } else if (attr == R.styleable.app_scaleHeight) {    scaleHeight = a.getDimensionPixelOffset(attr, 5)   } else if (attr == R.styleable.app_cursorColor) {    cursorColor = a.getColor(attr, context.resources.getColor(R.color.red))   } else if (attr == R.styleable.app_scaleColor) {    scaleColor = a.getColor(attr, context.resources.getColor(R.color.grey_888))   } else if (attr == R.styleable.app_scalePointerColor) {    scalePointerColor = a.getColor(attr, context.resources.getColor(R.color.grey_800))   }  }  cursorTextOffsetLeft = dpToPx(context, 32f)  a.recycle() } /**  * 初始化畫筆、滾動控制器和手勢對象  */ private fun init() {  scroller = OverScroller(context)  mGestureDetector = GestureDetectorCompat(context, onGestureListener)  maxFlingVelocity = ViewConfiguration.get(context).scaledMaximumFlingVelocity  minFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity  touchSlop = ViewConfiguration.get(context).scaledTouchSlop  scalePaint = Paint(Paint.ANTI_ALIAS_FLAG)  scalePaint.color = scaleColor  scalePaint.style = Paint.Style.FILL  scalePointerPaint = Paint(Paint.ANTI_ALIAS_FLAG)  scalePointerPaint.color = scalePointerColor  scalePointerPaint.style = Paint.Style.FILL  scalePointerTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)  scalePointerTextPaint.color = scaleColor  scalePointerTextPaint.style = Paint.Style.FILL  scalePointerTextPaint.textSize = spToPx(context, 14f).toFloat()  cursorPaint = Paint(Paint.ANTI_ALIAS_FLAG)  cursorPaint.color = cursorColor  cursorPaint.style = Paint.Style.FILL  cursorTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)  cursorTextPaint.color = context.resources.getColor(R.color.black_232)  cursorTextPaint.style = Paint.Style.FILL  cursorTextPaint.textSize = spToPx(context, 32f).toFloat()  cursorLabelPaint = Paint(Paint.ANTI_ALIAS_FLAG)  cursorLabelPaint.color = scalePointerColor  cursorLabelPaint.style = Paint.Style.FILL  cursorLabelPaint.textSize = spToPx(context, 16f).toFloat() } /**  * 設置item數據  */ fun setItems(items: List<*>) {  this.items = items  this.scrollHeight = (height + (this.items.size - 1) * scaleHeight).toFloat()  post {   mCurrentOrigin.x = 0f   mCurrentOrigin.y = 0f   invalidate()  } } /**  * 獲取item數據  */ fun getItems(): List<*> {  return items } /**  * 設置標簽文字  */ fun setLabel(label: String) {  this.label = label  //重新初始化刻度左距離  initScaleLeft()  //通知重新繪制  invalidate() } /**  * 觸控事件交給mGestureDetector  */ override fun onTouchEvent(event: MotionEvent): Boolean {  val result = mGestureDetector.onTouchEvent(event)  //如果手指離開屏幕,并且沒有慣性滑動  if (event.action == MotionEvent.ACTION_UP && mCurrentFlingDirection == Direction.NONE) {   if (mCurrentScrollDirection == Direction.VERTICAL) {    //檢查是否需要對齊刻度    snapScroll()   }   mCurrentScrollDirection = Direction.NONE  }  return result } /**  * 計算View如何滑動  */ override fun computeScroll() {  super.computeScroll()  if (scroller.isFinished) {//滾動以及完成   if (mCurrentFlingDirection !== Direction.NONE) {    // Snap to day after fling is finished.    mCurrentFlingDirection = Direction.NONE    snapScroll()//檢查是否需要對齊刻度,如果需要,則自動滾動,讓游標與刻度對齊   }  } else {   //如果當前不處于滾動狀態,則再次檢查是否需要對齊刻度   if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) {    snapScroll()   } else if (scroller.computeScrollOffset()) {//檢查是否滾動完成,并且計算新的滾動坐標    mCurrentOrigin.y = scroller.currY.toFloat()//記錄當前y坐標    checkOriginY()//檢查坐標是否越界    ViewCompat.postInvalidateOnAnimation(this)//通知重新繪制   } else {//不作滾動    val startY = if (mCurrentOrigin.y > 0)     0f    else if (mCurrentOrigin.y < height - measuredHeight)     measuredHeight - scrollHeight    else     mCurrentOrigin.y    scroller.startScroll(0, startY.toInt(), 0, 0, 0)   }  } } /**  * 測量控件大小,并初始化一些必要的屬性  */ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {  super.onMeasure(widthMeasureSpec, heightMeasureSpec)  val width = measuredWidth  val height = measuredHeight  offsetHeight = height / 2 - scaleHeight / 2  scrollHeight = height + (items.size - 1f) * scaleHeight  initScaleLeft()  pointerScaleLeft = scaleLeft + scaleWidth - scalePointerWidth } /**  * 初始化刻度左間距  */ private fun initScaleLeft() {  val labelSize = measureTextSize(cursorLabelPaint, label)  scaleLeft = (measuredWidth - scalePointerWidth + cursorTextOffsetLeft + labelSize[0].toInt()) / 2 } override fun onDraw(canvas: Canvas) {  super.onDraw(canvas)  if (items.isEmpty()) return  //繪制刻度  drawScale(canvas)  //繪制游標  drawCursor(canvas) } private fun drawScale(canvas: Canvas) {  for (i in 0 until items.size) {//根據給定的item信息繪制刻度   val top = offsetHeight + mCurrentOrigin.y.toInt() + i * scaleHeight + scaleHeight / 2   if (0 == i % 10) {//繪制整10刻度    canvas.drawRect(RectF(pointerScaleLeft.toFloat(), top - scaleStrokeWidth,      pointerScaleLeft.toFloat() + scalePointerWidth,      top + scaleStrokeWidth),      scalePointerPaint)    if (Math.abs(getSelectedItem() - i) > 1) {//整10刻度有文字,所以需要計算文字位置,并繪制文字     val text = items[i].toString()     val size = measureTextSize(scalePointerTextPaint, text)     canvas.drawText(text, pointerScaleLeft - size[0] * 1.3f, top + size[1] / 2, scalePointerTextPaint)    }   } else {//繪制普通刻度    canvas.drawRect(RectF(scaleLeft.toFloat(), top - scaleStrokeWidth,      scaleLeft.toFloat() + scaleWidth,      top + scaleStrokeWidth),      scalePaint)   }  } } /**  * 繪制游標,這里也需要計算文字位置,包括item文字和標簽文字  */ private fun drawCursor(canvas: Canvas) {  val left = scaleLeft + scaleWidth - cursorWidth  val top = measuredHeight / 2f  canvas.drawRect(RectF(left.toFloat(), top.toInt() - scaleStrokeWidth,    left.toFloat() + cursorWidth, top.toInt() + scaleStrokeWidth),    cursorPaint)  val text = items[getSelectedItem()].toString()  val textSize = measureTextSize(cursorTextPaint, text)  val labelSize = measureTextSize(cursorLabelPaint, label)  val labelLeft = left - cursorTextOffsetLeft - labelSize[0]  val textOffset = (textSize[0] - labelSize[0]) / 2f  canvas.drawText(text, left - cursorTextOffsetLeft - textSize[0] + textOffset, top + textSize[1] / 2, cursorTextPaint)  canvas.drawText(label, labelLeft, top + textSize[1] + labelSize[1], cursorLabelPaint) }  private fun forceFinishScroll(): Boolean {  return scroller.currVelocity <= minFlingVelocity } /**  * 與刻度對齊  */ private fun snapScroll() {  scroller.computeScrollOffset()  val nearestOrigin = -getSelectedItem() * scaleHeight  mCurrentOrigin.y = nearestOrigin.toFloat()  ViewCompat.postInvalidateOnAnimation(this@RulerView) } /**  * 檢查y坐標越界  */ private fun checkOriginY() {  if (mCurrentOrigin.y > 0) mCurrentOrigin.y = 0f  if (mCurrentOrigin.y < measuredHeight - scrollHeight)   mCurrentOrigin.y = measuredHeight - scrollHeight } /**  * 獲取選中的item  */ fun getSelectedItem(): Int {  var index = -Math.round(mCurrentOrigin.y / scaleHeight)  if (index >= items.size) index = items.size - 1  if (index < 0) index = 0  return index } /**  * 設置選中item  */ fun setSelectedItem(index: Int) {  post {   mCurrentOrigin.y = -(scaleHeight * index).toFloat()   checkOriginY()   ViewCompat.postInvalidateOnAnimation(this@RulerView)   snapScroll()  } } /**  * 手勢監聽  */ private val onGestureListener = object : GestureDetector.SimpleOnGestureListener() {  /**   * 手指按下回調,這里將狀態標記為非滾動狀態   */  override fun onDown(e: MotionEvent): Boolean {   parent.requestDisallowInterceptTouchEvent(true)   mCurrentScrollDirection = Direction.NONE   return true  }  /**   * 手指拖動回調   */  override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {   //如果當前正在滾動,則停止滾動   scroller.forceFinished(true)//   Log.e(RulerView::class.java, "onScroll: ${mCurrentOrigin.y}, $distanceY")   if (Direction.NONE == mCurrentScrollDirection) {//判斷滾動方向,這里只有垂直一個方向    mCurrentScrollDirection = if (Math.abs(distanceX) < Math.abs(distanceY)) {     Direction.VERTICAL    } else {     Direction.NONE    }   }   // Calculate the new origin after scroll.   when (mCurrentScrollDirection) {    Direction.VERTICAL -> {//計算手指拖動距離,并記錄新的坐標重繪界面     mCurrentOrigin.y -= distanceY     checkOriginY()     ViewCompat.postInvalidateOnAnimation(this@RulerView)    }   }   return true  }  /**   * 慣性滾動回調   */  override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {   scroller.forceFinished(true)   mCurrentFlingDirection = mCurrentScrollDirection   when (mCurrentFlingDirection) {    Direction.VERTICAL -> scroller.fling(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(),      0, velocityY.toInt(), Integer.MIN_VALUE,      Integer.MAX_VALUE, Integer.MIN_VALUE, 0)   }   ViewCompat.postInvalidateOnAnimation(this@RulerView)   return true  } } class ItemCreator {  companion object {   fun range(start: Int, end: Int): List<*> {    val result = ArrayList<Int>()    (start..end).forEach {     result.add(it)    }    return result   }  } } companion object {  fun dpToPx(context: Context, dp: Float): Int {   return Math.round(context.resources.displayMetrics.density * dp)  }  fun spToPx(context: Context, sp: Float): Int {   return (TypedValue.applyDimension(2, sp, context.resources.displayMetrics) + 0.5f).toInt()  }  /**   * 測量文字寬高   */  fun measureTextSize(paint: Paint, text: String): FloatArray {   if (TextUtils.isEmpty(text)) return floatArrayOf(0f, 0f)   val width = paint.measureText(text, 0, text.length)   val bounds = Rect()   paint.getTextBounds(text, 0, text.length, bounds)   return floatArrayOf(width, bounds.height().toFloat())  } }}

  attrs.xml

<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="app">  <attr name="scaleWidth" format="dimension" />  <attr name="scaleHeight" format="dimension" />  <attr name="cursorColor" format="color" />  <attr name="scaleColor" format="color" />  <attr name="scalePointerColor" format="color" /> </declare-styleable></resources>

  sample

<com.lava.demo.widget.RulerView android:id="@+id/timeRuler" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FAFAFA" app:cursorColor="#F48B2E" app:scaleColor="#C9C9C9" app:scaleHeight="8dp" app:scalePointerColor="#999999" app:scaleWidth="12dp" />

歡迎大家關注一下我開源的一個音視頻庫,HardwareVideoCodec是一個高效的Android音視頻編碼庫,支持軟編和硬編。使用它你可以很容易的實現任何分辨率的視頻編碼,無需關心攝像頭預覽大小。一切都如此簡單。目前已迭代多個穩定版本,歡迎查閱學習和使用,如有BUG或建議,歡迎Issue。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 久治县| 方山县| 陵水| 岳西县| 黑龙江省| 毕节市| 惠安县| 同德县| 崇仁县| 四川省| 民乐县| 榆中县| 永清县| 织金县| 锡林浩特市| 辉南县| 武清区| 衡山县| 湖州市| 榆林市| 商河县| 醴陵市| 南城县| 万荣县| 桂林市| 绥德县| 威信县| 台南县| 黔西| 共和县| 永胜县| 铅山县| 南平市| 昭平县| 望奎县| 晋宁县| 元氏县| 富蕴县| 诏安县| 交城县| 屏边|