本篇文章讲的是Kotlin 自定义view之实现标尺控件Ruler,以选择身高、体重等。开发中,当我们需要获取用户的身高和体重等信息时,如果直接让他们输入,显然体验不够好。像类似于唯品会、好轻等APP都是使用了类似于刻度尺的控件让用户滑动选择身高体重,觉得很棒。网上已有人使用Java语言实现这样的功能,但不影响我对其的学习。和往常一样,主要还是想总结一下自定义view之实现标尺控件的开发过程以及一些需要注意的地方。

按照惯例,我们先来看看效果图

一、先总结下自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
3、重写onMesure
4、重写onDraw
其中onMesure方法不一定要重写,但大部分情况下还是需要重写的

二、View 的几个构造函数
1、constructor(mContext: Context)
—>java代码直接new一个RulerView实例的时候,会调用这个只有一个参数的构造函数;
2、constructor(mContext: Context, attrs: AttributeSet)
—>在默认的XML布局文件中创建的时候调用这个有两个参数的构造函数。AttributeSet类型的参数负责把XML布局文件中所自定义的属性通过AttributeSet带入到View内;
3、constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int)
—>构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或者Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用
4、constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int,defStyleRes:Int)
—>该构造函数是在API21的时候才添加上的

三、下面我们就开始来看看代码啦
1、自定义View的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的需要用到的属性以及声明相对应属性的取值类型

<android.support.constraint.ConstraintLayout 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"><TextViewandroid:id="@+id/tv_weight_tip"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="体重"android:textColor="@android:color/black"android:textSize="14dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.132" /><RelativeLayoutandroid:id="@+id/rl_weight_ruler"android:layout_width="match_parent"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@+id/tv_weight_tip"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"><per.lijuan.rulerdome.RulerViewandroid:id="@+id/ruler_weight"android:layout_width="match_parent"android:layout_height="58dp"android:layout_marginTop="24dp"app:alphaEnable="true"app:lineColor="@android:color/darker_gray"app:lineMaxHeight="40dp"app:lineMidHeight="30dp"app:lineMinHeight="20dp"app:lineSpaceWidth="10dp"app:lineWidth="2.5dp"app:textColor="@android:color/black"app:minValue="20"app:maxValue="200"app:perValue="0.1"app:selectorValue="55"/><ImageViewandroid:layout_width="14dp"android:layout_height="46dp"android:layout_centerHorizontal="true"android:layout_marginTop="6dp"android:scaleType="fitXY"android:src="@mipmap/ic_arrow"/></RelativeLayout><TextViewandroid:id="@+id/tv_weight"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="11dp"android:maxHeight="30sp"android:textColor="@color/colorPrimary"android:textSize="24sp"app:layout_constraintTop_toBottomOf="@+id/rl_weight_ruler"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"/>
</android.support.constraint.ConstraintLayout>

一定要引入xmlns:app=”http://schemas.android.com/apk/res-auto”,Android Studio中我们可以使用res-atuo命名空间,就不用在添加自定义View全类名。

3、在View的构造方法中,获得我们的自定义的样式

private var mMinVelocity:Int = 0private var mScroller: Scroller? = null//Scroller是一个专门用于处理滚动效果的工具类   用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动private var mVelocityTracker: VelocityTracker?=null//主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。private var mWidth:Int = 0private var mHeight:Int = 0private var mSelectorValue=50f      // 未选择时 默认的值 滑动后表示当前中间指针正在指着的值private var mMaxValue=200f          // 最大数值private var mMinValue=100f          //最小的数值private var mPerValue=1f            //最小单位(如 1:表示每2条刻度差为1;0.1:表示每2条刻度差为0.1private var mLineSpaceWidth = 5f    //  尺子刻度2条线之间的距离private var mLineWidth = 4f         //  尺子刻度的宽度private var mLineMaxHeight = 420f   //  尺子刻度分为3中不同的高度。 mLineMaxHeight表示最长的那根(也就是 10的倍数时的高度)private var mLineMidHeight = 30f    //  mLineMidHeight  表示中间的高度(也就是 5  15 25 等时的高度)private var mLineMinHeight = 17f    //  mLineMinHeight  表示最短的那个高度(也就是 1 2 3 4 等时的高度)private var mTextMarginTop = 10fprivate var mTextSize = 30f          //尺子刻度下方数字的大小private var mAlphaEnable=false       // 尺子 最左边 最后边是否需要透明 `(透明效果更好点)private var mTextHeight = 0.toFloat()//尺子刻度下方数字的高度private var mTextPaint: Paint?=null   // 尺子刻度下方数字(也就是每隔10个出现的数值)画笔private var mLinePaint: Paint?=null   //  尺子刻度线的画笔private var mTotalLine:Int = 0       //共有多少条 刻度private var mMaxOffset:Int = 0       //所有刻度 共有多长private var mOffset:Float = 0.toFloat()// 默认状态下,mSelectorValue所在的位置  位于尺子总刻度的位置private var mLastX:Int = 0private var mMove: Int = 0private lateinit var mListener: OnValueChangeListener// 滑动后数值回调private var mLineColor:Int= Color.GRAY //刻度的颜色private var mTextColor:Int= Color.BLACK//文字的颜色constructor(mContext: Context) : super(mContext,null)constructor(mContext: Context, attrs: AttributeSet) : super(mContext, attrs,0)constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int) : super(mContext, attrs,defStyleAttr) {init(mContext, attrs)}fun init(context: Context, attrs: AttributeSet){Log.d(TAG, "init")mScroller= Scroller(context)this.mLineSpaceWidth=myfloat(25.0f)this.mLineWidth=myfloat(2.0f)this.mLineMaxHeight=myfloat(100.0f)this.mLineMidHeight=myfloat(60.0f)this.mLineMinHeight=myfloat(40.0f)this.mTextHeight=myfloat(40.0f)val typedArray: TypedArray =context.obtainStyledAttributes(attrs,R.styleable.RulerView)mAlphaEnable= typedArray.getBoolean(R.styleable.RulerView_alphaEnable, mAlphaEnable)mLineSpaceWidth = typedArray.getDimension(R.styleable.RulerView_lineSpaceWidth, mLineSpaceWidth)mLineWidth = typedArray.getDimension(R.styleable.RulerView_lineWidth, mLineWidth)mLineMaxHeight = typedArray.getDimension(R.styleable.RulerView_lineMaxHeight, mLineMaxHeight)mLineMidHeight = typedArray.getDimension(R.styleable.RulerView_lineMidHeight, mLineMidHeight)mLineMinHeight = typedArray.getDimension(R.styleable.RulerView_lineMinHeight, mLineMinHeight)mLineColor = typedArray.getColor(R.styleable.RulerView_lineColor, mLineColor)mTextSize = typedArray.getDimension(R.styleable.RulerView_textSize, mTextSize)mTextColor = typedArray.getColor(R.styleable.RulerView_textColor, mTextColor)mTextMarginTop = typedArray.getDimension(R.styleable.RulerView_textMarginTop, mTextMarginTop)mSelectorValue = typedArray.getFloat(R.styleable.RulerView_selectorValue, 0.0f)mMinValue = typedArray.getFloat(R.styleable.RulerView_minValue, 0.0f)mMaxValue = typedArray.getFloat(R.styleable.RulerView_maxValue, 100.0f)mPerValue = typedArray.getFloat(R.styleable.RulerView_perValue, 0.1f)mMinVelocity= ViewConfiguration.get(getContext()).scaledMinimumFlingVelocitymTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)mTextPaint!!.textSize = mTextSizemTextPaint!!.color = mTextColormTextHeight = getFontHeight(mTextPaint!!)mLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)mLinePaint!!.strokeWidth = mLineWidthmLinePaint!!.color = mLineColor}

我们重写了3个构造方法,在上面的构造方法中说过默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用三个参数的构造方法,然后在三个参数的构造方法中获得自定义属性。
一开始一个参数的构造方法和两个参数的构造方法是这样的:

constructor(mContext: Context) : super (mContext)constructor(mContext: Context, attrs: AttributeSet?) : super(mContext, attrs)

有一点要注意的是super应该改成this,然后让一个参数的构造方法引用两个参数的构造方法,两个参数的构造方法引用三个参数的构造方法,代码如下:

constructor(mContext: Context) : this(mContext,null)constructor(mContext: Context, attrs: AttributeSet?) : this(mContext, attrs!!,0)constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int) : super(mContext, attrs,defStyleAttr) {init(mContext, attrs)}

4、重写onDraw方法

override fun onDraw(canvas: Canvas) {super.onDraw(canvas)var left: Floatvar height: Floatvar value: Stringvar alpha = 0var scale: Floatval srcPointX = mWidth / 2for (i in 0 until mTotalLine) {left = srcPointX.toFloat() + mOffset + i * mLineSpaceWidthif (left < 0 || left > mWidth) {continue  //先画默认值在正中间,左右各一半的view。多余部分暂时不画(也就是从默认值在中间,画旁边左右的刻度线)}if (i % 10 == 0) {height = mLineMaxHeight} else if (i % 5 == 0) {height = mLineMidHeight} else {height = mLineMinHeight}if (mAlphaEnable) {scale = 1 - Math.abs(left - srcPointX) / srcPointXalpha = (255f * scale * scale).toInt()mLinePaint!!.setAlpha(alpha)}canvas.drawLine(left, 0f, left, height, mLinePaint)if (i % 10 == 0) {value = (mMinValue + i * mPerValue / 10).toInt().toString()if (mAlphaEnable) {mTextPaint!!.alpha = alpha}canvas.drawText(value, left - mTextPaint!!.measureText(value) / 2,height + mTextMarginTop + mTextHeight, mTextPaint)    // 在为整数时,画 数值}}}

View的绘制流程是从ViewRoot的performTravarsals方法开始的,经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中:

测量——onMeasure():用来测量View的宽和高来决定View的大小
布局——onLayout():用来确定View在父容器ViewGroup中的放置位置
绘制——onDraw():负责将View绘制在屏幕上

5、重写onTouchEvent方法
onTouchEvent()是View自带的接口,Android系统提供了默认的实现,用于处理触摸事件。当我们对标尺控件向左向右滑动时,此方法就会被调用。

override fun onTouchEvent(event: MotionEvent): Boolean {Log.d(TAG, "onTouchEvent")val action = event.actionval xPosition = event.x.toInt()if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain()}mVelocityTracker!!.addMovement(event)when (action) {MotionEvent.ACTION_DOWN -> {mScroller!!.forceFinished(true)mLastX = xPositionmMove = 0}MotionEvent.ACTION_MOVE -> {mMove = mLastX - xPositionchangeMoveAndValue()}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {countMoveEnd()countVelocityTracker()return false}else -> {}}mLastX = xPositionreturn true}

现在我把完整的代码贴出来

package per.lijuan.rulerdomeimport android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.widget.Scroller/*** Created by juan on 2018/5/11.*/
class RulerView: View {private val TAG : String = "RulerView"private var mMinVelocity:Int = 0private var mScroller: Scroller? = null//Scroller是一个专门用于处理滚动效果的工具类   用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动private var mVelocityTracker: VelocityTracker?=null//主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。private var mWidth:Int = 0private var mHeight:Int = 0private var mSelectorValue=50f      // 未选择时 默认的值 滑动后表示当前中间指针正在指着的值private var mMaxValue=200f          // 最大数值private var mMinValue=100f          //最小的数值private var mPerValue=1f            //最小单位(如 1:表示每2条刻度差为1;0.1:表示每2条刻度差为0.1private var mLineSpaceWidth = 5f    //  尺子刻度2条线之间的距离private var mLineWidth = 4f         //  尺子刻度的宽度private var mLineMaxHeight = 420f   //  尺子刻度分为3中不同的高度。 mLineMaxHeight表示最长的那根(也就是 10的倍数时的高度)private var mLineMidHeight = 30f    //  mLineMidHeight  表示中间的高度(也就是 5  15 25 等时的高度)private var mLineMinHeight = 17f    //  mLineMinHeight  表示最短的那个高度(也就是 1 2 3 4 等时的高度)private var mTextMarginTop = 10fprivate var mTextSize = 30f          //尺子刻度下方数字的大小private var mAlphaEnable=false       // 尺子 最左边 最后边是否需要透明 `(透明效果更好点)private var mTextHeight = 0.toFloat()//尺子刻度下方数字的高度private var mTextPaint: Paint?=null   // 尺子刻度下方数字(也就是每隔10个出现的数值)画笔private var mLinePaint: Paint?=null   //  尺子刻度线的画笔private var mTotalLine:Int = 0       //共有多少条 刻度private var mMaxOffset:Int = 0       //所有刻度 共有多长private var mOffset:Float = 0.toFloat()// 默认状态下,mSelectorValue所在的位置  位于尺子总刻度的位置private var mLastX:Int = 0private var mMove: Int = 0private lateinit var mListener: OnValueChangeListener// 滑动后数值回调private var mLineColor:Int= Color.GRAY //刻度的颜色private var mTextColor:Int= Color.BLACK//文字的颜色constructor(mContext: Context) : this(mContext,null)constructor(mContext: Context, attrs: AttributeSet?) : this(mContext, attrs!!,0)constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int) : super(mContext, attrs,defStyleAttr) {init(mContext, attrs)}fun init(context: Context, attrs: AttributeSet){Log.d(TAG, "init")mScroller= Scroller(context)this.mLineSpaceWidth=myfloat(25.0f)this.mLineWidth=myfloat(2.0f)this.mLineMaxHeight=myfloat(100.0f)this.mLineMidHeight=myfloat(60.0f)this.mLineMinHeight=myfloat(40.0f)this.mTextHeight=myfloat(40.0f)val typedArray: TypedArray =context.obtainStyledAttributes(attrs,R.styleable.RulerView)mAlphaEnable= typedArray.getBoolean(R.styleable.RulerView_alphaEnable, mAlphaEnable)mLineSpaceWidth = typedArray.getDimension(R.styleable.RulerView_lineSpaceWidth, mLineSpaceWidth)mLineWidth = typedArray.getDimension(R.styleable.RulerView_lineWidth, mLineWidth)mLineMaxHeight = typedArray.getDimension(R.styleable.RulerView_lineMaxHeight, mLineMaxHeight)mLineMidHeight = typedArray.getDimension(R.styleable.RulerView_lineMidHeight, mLineMidHeight)mLineMinHeight = typedArray.getDimension(R.styleable.RulerView_lineMinHeight, mLineMinHeight)mLineColor = typedArray.getColor(R.styleable.RulerView_lineColor, mLineColor)mTextSize = typedArray.getDimension(R.styleable.RulerView_textSize, mTextSize)mTextColor = typedArray.getColor(R.styleable.RulerView_textColor, mTextColor)mTextMarginTop = typedArray.getDimension(R.styleable.RulerView_textMarginTop, mTextMarginTop)mSelectorValue = typedArray.getFloat(R.styleable.RulerView_selectorValue, 0.0f)mMinValue = typedArray.getFloat(R.styleable.RulerView_minValue, 0.0f)mMaxValue = typedArray.getFloat(R.styleable.RulerView_maxValue, 100.0f)mPerValue = typedArray.getFloat(R.styleable.RulerView_perValue, 0.1f)mMinVelocity= ViewConfiguration.get(getContext()).scaledMinimumFlingVelocitymTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)mTextPaint!!.textSize = mTextSizemTextPaint!!.color = mTextColormTextHeight = getFontHeight(mTextPaint!!)mLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)mLinePaint!!.strokeWidth = mLineWidthmLinePaint!!.color = mLineColor}private fun myfloat(paramFloat:Float):Float{return 0.5f+paramFloat*1.0f}private fun getFontHeight(paint: Paint):Float{val fm = paint.fontMetricsreturn fm.descent - fm.ascent}/*** 设置默认的参数* @param selectorValue 未选择时 默认的值 滑动后表示当前中间指针正在指着的值* @param minValue   最大数值* @param maxValue   最小的数值* @param per   最小单位(如1:表示每2条刻度差为1;0.1:表示每2条刻度差为0.1;其中身高mPerValue为1,体重mPerValue 为0.1)*/fun setValue(selectorValue: Float, minValue: Float, maxValue: Float, per: Float) {this.mSelectorValue = selectorValuethis.mMaxValue = maxValuethis.mMinValue = minValuethis.mPerValue = per * 10.0fthis.mTotalLine = ((mMaxValue * 10 - mMinValue * 10) / mPerValue).toInt() + 1mMaxOffset = (-(mTotalLine - 1) * mLineSpaceWidth).toInt()mOffset = (mMinValue - mSelectorValue) / mPerValue * mLineSpaceWidth * 10fLog.d(TAG, "mOffset:" + mOffset + ",mMaxOffset:" + mMaxOffset+ ",mTotalLine:" + mTotalLine)invalidate()visibility = View.VISIBLE}fun setOnValueChangeListener(listener: OnValueChangeListener) {mListener = listener}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)if (w > 0 && h > 0) {mWidth = wmHeight = h}}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)var left: Floatvar height: Floatvar value: Stringvar alpha = 0var scale: Floatval srcPointX = mWidth / 2for (i in 0 until mTotalLine) {left = srcPointX.toFloat() + mOffset + i * mLineSpaceWidthif (left < 0 || left > mWidth) {continue  //先画默认值在正中间,左右各一半的view。多余部分暂时不画(也就是从默认值在中间,画旁边左右的刻度线)}if (i % 10 == 0) {height = mLineMaxHeight} else if (i % 5 == 0) {height = mLineMidHeight} else {height = mLineMinHeight}if (mAlphaEnable) {scale = 1 - Math.abs(left - srcPointX) / srcPointXalpha = (255f * scale * scale).toInt()mLinePaint!!.setAlpha(alpha)}canvas.drawLine(left, 0f, left, height, mLinePaint)if (i % 10 == 0) {value = (mMinValue + i * mPerValue / 10).toInt().toString()if (mAlphaEnable) {mTextPaint!!.alpha = alpha}canvas.drawText(value, left - mTextPaint!!.measureText(value) / 2,height + mTextMarginTop + mTextHeight, mTextPaint)    // 在为整数时,画 数值}}}override fun onTouchEvent(event: MotionEvent): Boolean {Log.d(TAG, "onTouchEvent")val action = event.actionval xPosition = event.x.toInt()if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain()}mVelocityTracker!!.addMovement(event)when (action) {MotionEvent.ACTION_DOWN -> {mScroller!!.forceFinished(true)mLastX = xPositionmMove = 0}MotionEvent.ACTION_MOVE -> {mMove = mLastX - xPositionchangeMoveAndValue()}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {countMoveEnd()countVelocityTracker()return false}else -> {}}mLastX = xPositionreturn true}private fun countVelocityTracker() {Log.d(TAG, "countVelocityTracker")mVelocityTracker!!.computeCurrentVelocity(1000)  //初始化速率的单位val xVelocity = mVelocityTracker!!.xVelocity //当前的速度if (Math.abs(xVelocity) > mMinVelocity) {mScroller!!.fling(0, 0, xVelocity.toInt(), 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0)}}/*** 滑动结束后,若是指针在2条刻度之间时,改变mOffset 让指针正好在刻度上。*/private fun countMoveEnd() {mOffset -= mMove.toFloat()if (mOffset <= mMaxOffset) {mOffset = mMaxOffset.toFloat()} else if (mOffset >= 0) {mOffset = 0f}mLastX = 0mMove = 0mSelectorValue = mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpaceWidth) * mPerValue / 10.0fmOffset = (mMinValue - mSelectorValue) * 10.0f / mPerValue * mLineSpaceWidthnotifyValueChange()postInvalidate()}/*** 滑动后的操作*/private fun changeMoveAndValue() {mOffset -= mMove.toFloat()if (mOffset <= mMaxOffset) {mOffset = mMaxOffset.toFloat()mMove = 0mScroller!!.forceFinished(true)} else if (mOffset >= 0) {mMove = 0mScroller!!.forceFinished(true)}mSelectorValue = mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpaceWidth) * mPerValue / 10.0fnotifyValueChange()postInvalidate()}private fun notifyValueChange() {if (null != mListener) {mListener.onValueChange(mSelectorValue)}}/*** 滑动后的回调*/interface OnValueChangeListener{fun onValueChange(value: Float)}override fun computeScroll() {Log.d(TAG, "computeScroll")super.computeScroll()if (mScroller!!.computeScrollOffset()) {//mScroller.computeScrollOffset()返回true表示滑动还没有结束if (mScroller!!.currX == mScroller!!.finalX) {countMoveEnd()} else {val xPosition = mScroller!!.currXmMove = mLastX - xPositionchangeMoveAndValue()mLastX = xPosition}}}
}

在页面中,我们要给自定义的标尺设置默认的参数:未选择时默认的值、最大数值、最小的数值以及最小单位

//体重的viewmWeightRuler!!.setOnValueChangeListener(object : RulerView.OnValueChangeListener {override fun onValueChange(value: Float) {weight = valuemTvWeight!!.text = weight.toString() + "kg"}})mWeightRuler!!.setValue(55f, 20f, 200f, 0.1f)

参考资料:
https://github.com/panacena/RuleView

源码下载

有什么疑问的,请在下面留言,有不足的还望指导,感谢各位^_^

Kotlin 自定义View之实现标尺控件(选择身高、体重等)相关推荐

  1. android身高控件_RuleView Android 自定义标尺控件(选择身高、体重等) @codeKK Android开源站...

    尺子刻度 -- 自定义 view 自定义 view 学习(第一章) 1.自定义刻度尺控件 在我们想要获取用户的身高体重等信息时,直接让他们输入显然不够友好偶然看到一款 App 用了类似刻度尺的界面让用 ...

  2. android lrc 歌词view,自定义View强势来袭,用自定义View实现歌词显示控件下篇之自定义LyricView的实现...

    在上篇中,我与大家分享了关于如何进行*.lrc歌词文件的解析,以及将解析完成后的歌词展示在镶嵌在ScrollView中的TextView上,就这样而言,一个简单的歌词显示功能也就实现了. 但是,如何才 ...

  3. Android Paint应用之自定义View实现进度条控件

    在上一篇文章<Android神笔之Paint>学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 上图就是本文要 ...

  4. android歌词效果,自定义View:Android歌词控件

    TicktockMusic 音乐播放器项目相关文章汇总: 简介 之前做 TicktockMusic 音乐播放器,一个必要的需求肯定是歌词,在 github 上找了几个,发现或多或少都有点不满足需求,所 ...

  5. 自定义 View 循环滚动刻度控件

    LoopScaleView 先看效果图: enter description here LoopScaleView 是一个自定义的刻度尺风格的选值控件,从上面的动图大家可以看到 LoopScaleVi ...

  6. Android自定义View 多边形能力分析控件,雷达图(蛛网)动态实现

    自定义View实现雷达图还是挺简单的,它能让使用让使用者能一目了然的了解各项指标的变动情形以及好坏趋势.使用得最多的便是Path路径,很适合初学者用来练习. 效果图如下: 下面是实体类的属性: pub ...

  7. mysql抽屉图标_React Native自定义组件实现抽屉菜单控件效果

    一.需求分析 原生开发中,自定义View可谓是屡见不鲜的事情,往往系统的控件总不能满足现实的需求.五花八门的产品设计需要我们做出不同的View.关于自定义View的内容网上已经有很多的博文,本篇博客要 ...

  8. WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探

    原文:WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探 最近因为项目需要,开始学习如何使用WPF开发桌面程序.使用WPF一段时间之后,感觉WPF的开发思 ...

  9. 一瞬间-自定义一个漂亮的日期控件

    因为项目,需要一个日期输入控件,目前没有看到有特别合适的,所以自己DIY了一个,比较匆忙,说是一瞬间搞定,可也搞了2个小时才搞定的.    虽然其中使用事件和代码不是很规范,但目前可以凑合能用啦!,放 ...

最新文章

  1. swift 语言评价
  2. 【Flutter】遇见错误
  3. boost::log::sinks::file用法的测试程序
  4. 【Transformer】SMCA: Fast Convergence of DETR with Spatially Modulated Co-Attention
  5. Android之SwipeRefreshLayout嵌套RecyclerView遇到的坑
  6. pytorch中切换虚拟环境
  7. C语言基础知识之基本数据类型相关的总结
  8. code函数oracle列子,Oracle Pivot函数语法详解及应用实例
  9. php yii composer,yii2怎么用composer生成一个应用?
  10. OC学习笔记四 数据类型
  11. mac制作Windows10镜像
  12. 【HTML】HTML浏览器打印自定义页眉页脚
  13. next项目部署到服务器pm2进程守护
  14. unable to find valid certification path to requested target的异常解决办法
  15. Mybatis一发入魂
  16. 如何快速向oracle插入大量数据,以及注意事项
  17. (vant新手坑)引入Vant组件并改变其样式
  18. 详解 Java 日期与时间
  19. 【Data Science from Scratch 学习笔记】第2章 Python速成(上)
  20. 菜鸟haqima的Java学习之路第一天

热门文章

  1. 【Nginx】重启报错,端口重复占用无法解决
  2. VirtualXposed 不支持32位应用 32位无法安装问题解决办法
  3. java实现好友添加_SpringBoot+LayIM+t-io 实现好友申请通知流程
  4. 用友U8案例教程存货核算后台配置
  5. [转] 适合儿童上手的八款编程工具
  6. Matlab论文插图绘制模板第28期—折线图进阶
  7. 图形学基础知识(渲染管线)
  8. (一)少儿编程是什么、编程很难么,怎么学、学什么?
  9. 【pwn学习】Canary的各种绕过姿势
  10. 电信天翼云NB-IOT平台数据接入