Kotlin 基础语法实践

通过之前几篇文章,我们基本了解了 Kotlin 的开发基础语法,其中包括基本数据类型、控制语句、循环等。本篇文章是针对Kotlin基础语法篇做一个小的总结,并用几个具体的例子来体验一下 Kotlin 的开发。

1. 例子一(Kotlin实现一个二分查找算法)

1.1 涵盖有关 Kotlin 的知识

  • Kotlin 基础语法,其中包括函数定义、基本数据类型定义等;

  • Kotlin 条件控制 if-else 和 when;

  • Kotlin 循环控制;

  • Kotlin 中的运算符比如位运算、逻辑运算、算术运算等。

1.2 具体实现代码(递推实现)

fun main() {
    println(binarySearch(intArrayOf(1, 3, 5, 7, 9, 11), 5))
}

fun binarySearch(numberArray: IntArray, targetNum: Int): Int {
    var low = 0
    var high = numberArray.size - 1

    while (low <= high) {
        var mid = (low + high) ushr 1// ushr表示Kotlin中的中缀调用函数相当于Java中无符号右移位运算符>>>
        when {
            targetNum == numberArray[mid] -> return mid
            targetNum > numberArray[mid] -> low = mid + 1
            else -> high = mid - 1
        }
    }
    return -1
}

1.3 具体实现代码(递归实现)

fun main() {
    val numberArray = intArrayOf(1, 3, 5, 7, 9, 11)
    println(binarySearch(numberArray, 0, numberArray.size - 1, 5))
}

fun binarySearch(numberArray: IntArray, low: Int, high: Int, targetNum: Int): Int {
    if (low > high) {
        return -1
    }
    val mid = (low + high) ushr 1
    if (targetNum == numberArray[mid]) {
        return mid
    }
    return if (targetNum > numberArray[mid]) {
        binarySearch(numberArray, mid + 1, high, targetNum)
    } else {
        binarySearch(numberArray, low, mid - 1, targetNum)
    }
}

2. 例子二(Kotlin实现一个DCL单例模式)

2.1 涵盖有关 Kotlin 的知识

  • Kotlin 基础语法,其中包括函数定义、基本数据类型定义等;

  • Kotlin 条件控制 if-else 和 when;

  • Kotlin 循环控制;

  • Kotlin 中 Compaion Object 的用法(后面语法会有介绍);

  • Kotlin 中内置属性委托 by lazy 的使用(后面语法会有介绍);

  • Kotlin 中的 @JvmStatic 注解(后面语法会有介绍)。

2.2 具体实现代码(Kotlin 实现)

class KtDCLSingleton private constructor() : Serializable {//private constructor()构造器私有化

    fun doSomething() {
        println("do some thing")
    }

    private fun readResolve(): Any {//防止单例对象在反序列化时重新生成对象
        return instance
    }
    
    companion object {
        //通过@JvmStatic注解,使得在Java中调用instance直接是像调用静态函数一样,
        @JvmStatic
        //使用lazy属性代理,并指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全
        val instance: KtDCLSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { KtDCLSingleton() }
    }
}

//在Kotlin中调用,直接通过KtDCLSingleton类名调用instance
fun main(args: Array<String>) {
    KtDCLSingleton.instance.doSomething()
}

//在Java中调用
public class TestMain {
    public static void main(String[] args) {
       //加了@JvmStatic注解后,可以直接KtDCLSingleton.getInstance(),不会打破Java中调用习惯,和Java调用方式一样。
       KtDCLSingleton.getInstance().doSomething();
       //没有加@JvmStatic注解,只能这样通过Companion调用
       KtDCLSingleton.Companion.getInstance().doSomething();
    }
}

2.3 具体实现代码(Java实现)

//DCL实现单例模式
public class LazySingleton implements Serializable {
    //静态成员私有化,注意使用volatile关键字,因为会存在DCL失效的问题
    private volatile static LazySingleton mInstance = null; 

    private LazySingleton() { //构造器私有化
    }

    //公有获取单例对象的函数
    //DCL(Double Check Lock) 既能在需要的时候初始化单例,又能保证线程安全,且单例对象初始化完后,调用getInstance不需要进行同步锁
    public static LazySingleton getInstance() {
        if (mInstance == null) {//为了防止单例对象初始化完后,调用getInstance再次重复进行同步锁
            synchronized (LazySingleton.class) {
                if (mInstance == null) {
                    mInstance = new LazySingleton();
                }
            }
        }

        return mInstance;
    }

    private Object readResolve() throws ObjectStreamException {
        return mInstance;
    }
}

3. 例子三(Kotlin 实现 Android 中的自定义 View)

使用 Kotlin 实现 Android 中自定义圆形或圆角图片,需要支持以下功能:

  • 支持图片圆形的定制化;
  • 支持图片圆角以及每个角的X,Y方向值的定制化;
  • 支持形状边框宽度颜色的定制化;
  • 支持图片圆角或者圆形右上角消息圆点定制化(一般用于圆形或者圆角头像)。

3.1 涵盖有关 Kotlin 的知识

  • Kotlin 中基本语法知识;
  • Kotlin 中自定义属性访问器;
  • Kotlin 中默认值参数实现构造器函数重载以及 @JvmOverloads 注解的使用;
  • Kotlin 标准库中常见的apply,run,with函数的使用(后面语法会有介绍);
  • Kotlin 中默认值参数函数的使用(后面语法会有介绍)。

3.2 具体实现代码

import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.util.TypedValue
import android.widget.ImageView

class RoundImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
	: ImageView(context, attributeSet, defAttrStyle) {

	enum class ShapeType {
		SHAPE_CIRCLE,
		SHAPE_ROUND
	}

	//defAttr var
	private var mShapeType: ShapeType = ShapeType.SHAPE_CIRCLE
		set(value) {
			field = value
			invalidate()
		}
	private var mBorderWidth: Float = 20f
		set(value) {
			field = value
			invalidate()
		}
	private var mBorderColor: Int = Color.parseColor("#ff9900")
		set(value) {
			field = value
			invalidate()
		}

	private var mLeftTopRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mLeftTopRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightTopRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightTopRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mLeftBottomRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mLeftBottomRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightBottomRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightBottomRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mShowBorder: Boolean = true
		set(value) {
			field = value
			invalidate()
		}
	private var mShowCircleDot: Boolean = false
		set(value) {
			field = value
			invalidate()
		}
	private var mCircleDotColor: Int = Color.RED
		set(value) {
			field = value
			invalidate()
		}

	private var mCircleDotRadius: Float = 20f
		set(value) {
			field = value
			invalidate()
		}

	//drawTools var
	private lateinit var mShapePath: Path
	private lateinit var mBorderPath: Path
	private lateinit var mBitmapPaint: Paint
	private lateinit var mBorderPaint: Paint
	private lateinit var mCircleDotPaint: Paint
	private lateinit var mMatrix: Matrix

	//temp var
	private var mWidth: Int = 200//View的宽度
	private var mHeight: Int = 200//View的高度
	private var mRadius: Float = 100f//圆的半径

	init {
		initAttrs(context, attributeSet, defAttrStyle)//获取自定义属性的值
		initDrawTools()//初始化绘制工具
	}

	private fun initAttrs(context: Context, attributeSet: AttributeSet?, defAttrStyle: Int) {
		val array = context.obtainStyledAttributes(attributeSet, R.styleable.PrettyImageView, defAttrStyle, 0)
		(0..array.indexCount)
				.asSequence()
				.map { array.getIndex(it) }
				.forEach {
					when (it) {
						R.styleable.RoundImageView_shape_type ->
							mShapeType = when {
								array.getInt(it, 0) == 0 -> ShapeType.SHAPE_CIRCLE
								array.getInt(it, 0) == 1 -> ShapeType.SHAPE_ROUND
								else -> ShapeType.SHAPE_CIRCLE
							}
						R.styleable.RoundImageView_border_width ->
							mBorderWidth = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics))
						R.styleable.RoundImageView_border_color ->
							mBorderColor = array.getColor(it, Color.parseColor("#ff0000"))
						R.styleable.RoundImageView_left_top_radiusX ->
							mLeftTopRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_left_top_radiusY ->
							mLeftTopRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_left_bottom_radiusX ->
							mLeftBottomRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_left_bottom_radiusY ->
							mLeftBottomRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_right_bottom_radiusX ->
							mRightBottomRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_right_bottom_radiusY ->
							mRightBottomRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_right_top_radiusX ->
							mRightTopRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_right_top_radiusY ->
							mRightTopRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.RoundImageView_show_border ->
							mShowBorder = array.getBoolean(it, false)
						R.styleable.RoundImageView_show_circle_dot ->
							mShowCircleDot = array.getBoolean(it, false)
						R.styleable.RoundImageView_circle_dot_color ->
							mCircleDotColor = array.getColor(it, Color.parseColor("#ff0000"))
						R.styleable.RoundImageView_circle_dot_radius ->
							mCircleDotRadius = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics))
					}
				}
		array.recycle()
	}

	private fun initDrawTools() {
		mBitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
			//最终绘制图片的画笔,需要设置BitmapShader着色器,从而实现把图片绘制在不同形状图形上
			style = Paint.Style.FILL
		}
		mBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
			//绘制边框画笔
			style = Paint.Style.STROKE
			color = mBorderColor
			strokeCap = Paint.Cap.ROUND
			strokeWidth = mBorderWidth
		}
		mCircleDotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
			//绘制右上角圆点画笔
			style = Paint.Style.FILL
			color = mCircleDotColor
		}
		mShapePath = Path()//描述形状轮廓的path路径
		mBorderPath = Path()//描述图片边框轮廓的path路径
		mMatrix = Matrix()//用于缩放图片的矩阵
		scaleType = ScaleType.CENTER_CROP
	}

	override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {//View的测量
		super.onMeasure(widthMeasureSpec, heightMeasureSpec)
		if (mShapeType == ShapeType.SHAPE_CIRCLE) {
			mWidth = Math.min(measuredWidth, measuredHeight)
			mRadius = mWidth / 2.0f
			setMeasuredDimension(mWidth, mWidth)
		} else {
			mWidth = measuredWidth
			mHeight = measuredHeight
			setMeasuredDimension(mWidth, mHeight)
		}
	}

	override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {//确定了最终View的尺寸
		super.onSizeChanged(w, h, oldw, oldh)
		mBorderPath.reset()
		mShapePath.reset()
		when (mShapeType) {
			ShapeType.SHAPE_ROUND -> {
				mWidth = w
				mHeight = h
				buildRoundPath()
			}
			ShapeType.SHAPE_CIRCLE -> {
				buildCirclePath()
			}
		}
	}

	private fun buildCirclePath() {//构建圆形类型的Path路径
		if (!mShowBorder) {//绘制不带边框的圆形实际上只需要把一个圆形扔进path即可
			mShapePath.addCircle(mRadius, mRadius, mRadius, Path.Direction.CW)
		} else {//绘制带边框的圆形需要把内部圆形和外部圆形边框都要扔进path
			mShapePath.addCircle(mRadius, mRadius, mRadius - mBorderWidth, Path.Direction.CW)
			mBorderPath.addCircle(mRadius, mRadius, mRadius - mBorderWidth / 2.0f, Path.Direction.CW)
		}
	}

	private fun buildRoundPath() {//构建圆角类型的Path路径
		if (!mShowBorder) {//绘制不带边框的圆角实际上只需要把一个圆角矩形扔进path即可
			floatArrayOf(mLeftTopRadiusX, mLeftTopRadiusY,
					mRightTopRadiusX, mRightTopRadiusY,
					mRightBottomRadiusX, mRightBottomRadiusY,
					mLeftBottomRadiusX, mLeftBottomRadiusY).run {
				mShapePath.addRoundRect(RectF(0f, 0f, mWidth.toFloat(), mHeight.toFloat()), this, Path.Direction.CW)
			}

		} else {//绘制带边框的圆角实际上只需要把一个圆角矩形和一个圆角矩形的变量都扔进path即可
			floatArrayOf(mLeftTopRadiusX - mBorderWidth / 2.0f, mLeftTopRadiusY - mBorderWidth / 2.0f,
					mRightTopRadiusX - mBorderWidth / 2.0f, mRightTopRadiusY - mBorderWidth / 2.0f,
					mRightBottomRadiusX - mBorderWidth / 2.0f, mRightBottomRadiusY - mBorderWidth / 2.0f,
					mLeftBottomRadiusX - mBorderWidth / 2.0f, mLeftBottomRadiusY - mBorderWidth / 2.0f).run {
				mBorderPath.addRoundRect(RectF(mBorderWidth / 2.0f, mBorderWidth / 2.0f, mWidth.toFloat() - mBorderWidth / 2.0f, mHeight.toFloat() - mBorderWidth / 2.0f), this, Path.Direction.CW)
			}

			floatArrayOf(mLeftTopRadiusX - mBorderWidth, mLeftTopRadiusY - mBorderWidth,
					mRightTopRadiusX - mBorderWidth, mRightTopRadiusY - mBorderWidth,
					mRightBottomRadiusX - mBorderWidth, mRightBottomRadiusY - mBorderWidth,
					mLeftBottomRadiusX - mBorderWidth, mLeftBottomRadiusY - mBorderWidth).run {
				mShapePath.addRoundRect(RectF(mBorderWidth, mBorderWidth, mWidth.toFloat() - mBorderWidth, mHeight.toFloat() - mBorderWidth),
						this, Path.Direction.CW)
			}

		}
	}

	override fun onDraw(canvas: Canvas?) {//由于经过以上根据不同逻辑构建了boderPath和shapePath,path中已经储存相应的形状,现在只需要把相应shapePath中形状用带BitmapShader画笔绘制出来,boderPath用普通画笔绘制出来即可
		drawable ?: return
		mBitmapPaint.shader = getBitmapShader()//获得相应的BitmapShader着色器对象
		when (mShapeType) {
			ShapeType.SHAPE_CIRCLE -> {
				if (mShowBorder) {
					canvas?.drawPath(mBorderPath, mBorderPaint)//绘制圆形图片边框path
				}
				canvas?.drawPath(mShapePath, mBitmapPaint)//绘制圆形图片形状path
				if (mShowCircleDot) {
					drawCircleDot(canvas)//绘制圆形图片右上角圆点
				}
			}
			ShapeType.SHAPE_ROUND -> {
				if (mShowBorder) {
					canvas?.drawPath(mBorderPath, mBorderPaint)//绘制圆角图片边框path
				}
				canvas?.drawPath(mShapePath, mBitmapPaint)//绘制圆角图片形状path
			}
		}

	}

	private fun drawCircleDot(canvas: Canvas?) {
		canvas?.run {
			drawCircle((mRadius + mRadius * (Math.sqrt(2.0) / 2.0f)).toFloat(), (mRadius - mRadius * (Math.sqrt(2.0) / 2.0f)).toFloat(), mCircleDotRadius, mCircleDotPaint)
		}
	}

	private fun getBitmapShader(): BitmapShader {
		val bitmap = drawableToBitmap(drawable)
		return BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP).apply {
			var scale = 1.0f
			if (mShapeType == ShapeType.SHAPE_CIRCLE) {
				scale = (mWidth * 1.0f / Math.min(bitmap.width, bitmap.height))
			} else if (mShapeType == ShapeType.SHAPE_ROUND) {
				// 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;
				if (!(width == bitmap.width && width == bitmap.height)) {
					scale = Math.max(width * 1.0f / bitmap.width, height * 1.0f / bitmap.height)
				}
			}
			// shader的变换矩阵,我们这里主要用于放大或者缩小
			mMatrix.setScale(scale, scale)
			setLocalMatrix(mMatrix)
		}
	}

	private fun drawableToBitmap(drawable: Drawable): Bitmap {
		if (drawable is BitmapDrawable) {
			return drawable.bitmap
		}
		return Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888).apply {
			drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
			drawable.draw(Canvas(this@apply))
		}
	}

	companion object {
		private const val STATE_INSTANCE = "state_instance"
		private const val STATE_INSTANCE_SHAPE_TYPE = "state_shape_type"
		private const val STATE_INSTANCE_BORDER_WIDTH = "state_border_width"
		private const val STATE_INSTANCE_BORDER_COLOR = "state_border_color"
		private const val STATE_INSTANCE_RADIUS_LEFT_TOP_X = "state_radius_left_top_x"
		private const val STATE_INSTANCE_RADIUS_LEFT_TOP_Y = "state_radius_left_top_y"
		private const val STATE_INSTANCE_RADIUS_LEFT_BOTTOM_X = "state_radius_left_bottom_x"
		private const val STATE_INSTANCE_RADIUS_LEFT_BOTTOM_Y = "state_radius_left_bottom_y"
		private const val STATE_INSTANCE_RADIUS_RIGHT_TOP_X = "state_radius_right_top_x"
		private const val STATE_INSTANCE_RADIUS_RIGHT_TOP_Y = "state_radius_right_top_y"
		private const val STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_X = "state_radius_right_bottom_x"
		private const val STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_Y = "state_radius_right_bottom_y"
		private const val STATE_INSTANCE_RADIUS = "state_radius"
		private const val STATE_INSTANCE_SHOW_BORDER = "state_radius_show_border"
	}

	//View State Save
	override fun onSaveInstanceState(): Parcelable = Bundle().apply {
		putParcelable(STATE_INSTANCE, super.onSaveInstanceState())
		putInt(STATE_INSTANCE_SHAPE_TYPE, when (mShapeType) {
			ShapeType.SHAPE_CIRCLE -> 0
			ShapeType.SHAPE_ROUND -> 1
		})
		putFloat(STATE_INSTANCE_BORDER_WIDTH, mBorderWidth)
		putInt(STATE_INSTANCE_BORDER_COLOR, mBorderColor)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_X, mLeftTopRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_Y, mLeftTopRadiusY)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_X, mLeftBottomRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_Y, mLeftBottomRadiusY)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_X, mRightTopRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_Y, mRightTopRadiusY)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_X, mRightBottomRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_Y, mRightBottomRadiusY)
		putFloat(STATE_INSTANCE_RADIUS, mRadius)
		putBoolean(STATE_INSTANCE_SHOW_BORDER, mShowBorder)
	}

	//View State Restore
	override fun onRestoreInstanceState(state: Parcelable?) {
		if (state !is Bundle) {
			super.onRestoreInstanceState(state)
			return
		}

		with(state) {
			super.onRestoreInstanceState(getParcelable(STATE_INSTANCE))
			mShapeType = when {
				getInt(STATE_INSTANCE_SHAPE_TYPE) == 0 -> ShapeType.SHAPE_CIRCLE
				getInt(STATE_INSTANCE_SHAPE_TYPE) == 1 -> ShapeType.SHAPE_ROUND
				else -> ShapeType.SHAPE_CIRCLE
			}
			mBorderWidth = getFloat(STATE_INSTANCE_BORDER_WIDTH)
			mBorderColor = getInt(STATE_INSTANCE_BORDER_COLOR)
			mLeftTopRadiusX = getFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_X)
			mLeftTopRadiusY = getFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_Y)
			mLeftBottomRadiusX = getFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_X)
			mLeftBottomRadiusY = getFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_Y)
			mRightTopRadiusX = getFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_X)
			mRightTopRadiusY = getFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_Y)
			mRightBottomRadiusX = getFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_X)
			mRightBottomRadiusY = getFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_Y)
			mRadius = getFloat(STATE_INSTANCE_RADIUS)
			mShowBorder = getBoolean(STATE_INSTANCE_SHOW_BORDER)
		}
	}
}

4. 总结

到这里有关 Kotlin 基础语法实践篇就结束了,可能会看到里面涉及到一些没介绍的语法点。后续的语法知识都会有涉及到的,感兴趣的小伙伴可以提前预习下,并且结合这个例子练习一遍,相信你对Kotlin 的基础语法掌握还是很牢固的。