在小小的屏幕上组合出符合预期的视图,仅仅使用已经定义好的组件可不够,需要我们自己去设置视图。
先看看View的结构:
1. 首先介绍,自定义View滑动。一共有六种方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scrollTo与ScrollBy以及Scroller。
- Layout的使用。
我们自定义一个View。为CustomView.kt。
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import java.time.format.DecimalStyle
class CustomView(val contex: Context,val attrs:AttributeSet?) : View(contex, attrs) {
private var lastX:Int = 0
private var lastY:Int = 0
override fun onTouchEvent(event: MotionEvent?): Boolean {
var x:Int = event!!.x.toInt()
var y:Int = event!!.y.toInt()
when(event.action){
MotionEvent.ACTION_DOWN -> {
lastX = x
lastY = y
}
MotionEvent.ACTION_MOVE ->{
var offsetX:Int = x - lastX
var offsetY:Int = y - lastY
layout(left + offsetX, top + offsetY, right + offsetX, bottom + offsetY)
}
}
return true
}
}
在视图直接使用就行,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.my_view.ui.layout.CustomView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="50dp"
android:background="#8742A8A3"
/>
</LinearLayout>
拖拉这个视图,就可以实现移动了。
- offsetLeftAndRight()与offsetTopAndBottom()
上面代码改一下就可以了
var offsetX:Int = x - lastX
var offsetY:Int = y - lastY
offsetLeftAndRight(offsetX)
offsetTopAndBottom(offsetY)
- LayoutParams同样更改一个地方
var offsetX:Int = x - lastX
var offsetY:Int = y - lastY
// offsetLeftAndRight(offsetX)
// offsetTopAndBottom(offsetY)
var layoutParams:LinearLayout.LayoutParams = getLayoutParams() as LinearLayout.LayoutParams
layoutParams.leftMargin = left + offsetX
layoutParams.topMargin = top + offsetY
setLayoutParams(layoutParams)
- 动画
在res/anim文件夹下创建translate.xml,设置好动画。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
>
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300"
android:fromYDelta="0"
android:toYDelta="200"
/>
</set>
在主活动中设置动画,
setContentView(R.layout.main_activity)
val mCustomView:CustomView = findViewById(R.id.view)
mCustomView.animation = AnimationUtils.loadAnimation(this, R.anim.translate)
- scrollTo与scollBy需要注意,这个滑动指的是父组件。
同样替换ACTION_MOVE的部分。需要记得去掉上面的动画,否则不生效。
(parent as View).scrollBy(-offsetX, -offsetY)
- Scroller的使用
修改CustomView.kt。
package com.example.my_view.ui.layout
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.Scroller
import java.time.format.DecimalStyle
class CustomView(val contex: Context,val attrs:AttributeSet?) : View(contex, attrs) {
private var lastX:Int = 0
private var lastY:Int = 0
private var mScroller:Scroller? = null
init {
this.mScroller = Scroller(context)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
var x:Int = event!!.x.toInt()
var y:Int = event!!.y.toInt()
when(event.action){
MotionEvent.ACTION_DOWN -> {
lastX = x
lastY = y
}
MotionEvent.ACTION_MOVE ->{
var offsetX:Int = x - lastX
var offsetY:Int = y - lastY
// offsetLeftAndRight(offsetX)
// offsetTopAndBottom(offsetY)
// var layoutParams:LinearLayout.LayoutParams = getLayoutParams() as LinearLayout.LayoutParams
// layoutParams.leftMargin = left + offsetX
// layoutParams.topMargin = top + offsetY
// setLayoutParams(layoutParams)
// (parent as View).scrollBy(-offsetX, -offsetY)
}
}
return true
}
//重绘函数
override fun computeScroll() {
super.computeScroll()
if(mScroller!!.computeScrollOffset()){
(parent as View).scrollTo(mScroller!!.currX, mScroller!!.currY)
invalidate()
}
}
//新加的
fun smoothScrollTo(destX:Int, destY:Int){
var scrollX:Int = scrollX
var delta:Int = destX - scrollX
mScroller!!.startScroll(scrollX, 0, delta, 0 ,2000)
invalidate()
}
}
在主活动设置右移400。
val mCustomView:CustomView = findViewById(R.id.view)
mCustomView.smoothScrollTo(-400, 0)
OK,六种完事了!
2. 属性动画
基本的属性动画,包括四种:AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation。它们属于View的基本动画,使用便捷,但是,缺点也很明显,不具有交互性。使用AnimationSet和ObjectAnimator可以进行更加精细化的控制,下面来介绍。
- ObjectAnimator
作为动画属性中最为重要的属性,需要注意设置的属性,必须拥有Set和Get方法,尤其在使用自定义的属性时。
示例:
val mCustomView:CustomView = findViewById(R.id.view)
val mObjectAnimator:ObjectAnimator = ObjectAnimator.ofFloat(mCustomView, "translationX", 400F)
mObjectAnimator.duration = 3000
mObjectAnimator.start()
"translationX"为动画的属性,可以使用的还有:
- translationX与translationY,平移
- rotation、rotationX、rotationY。旋转
- PrivotX、PrivotY控制View对象的支点位置。
- alpha透明度.
- x和y描述在容器中的最终位置。
也可以自定义属性,记得设置set与get方法。
- 好了,下一个ValueAnimator
不提供动画效果,类似数值发生器,用来产生有一定规律的数字,让调用者控制动画的实现过程。
val mValueAnimator:ValueAnimator = ValueAnimator.ofFloat(0f ,100f)
mValueAnimator.setDuration(1000).start()
mValueAnimator.addUpdateListener {
val mFloat:Float = it.animatedValue as Float
}
- 动画的监听
start、Repeat、End、Cancel四个过程。 - 组合动画
AnimatorSet类提供了play()方法。
play(),with(),after(),before()
val animator1:ObjectAnimator = ObjectAnimator.ofFloat(mCustomView, "translationX", 0.0f, 200f, 0f)
val animator2:ObjectAnimator = ObjectAnimator.ofFloat(mCustomView, "scaleX", 1.0f, 2f)
val animator3:ObjectAnimator = ObjectAnimator.ofFloat(mCustomView, "rotationX", 0.0f, 90f, 0f)
val set:AnimatorSet = AnimatorSet()
set.duration = 1000
set.play(animator1).with(animator2).after(animator3)
set.start()
- XML中使用动画
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duration="3000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType"
>
</objectAnimator>
3.解析Scroller
4.View事件分发机制
- Activity构成
当点击事件产生之后,事件首先会传递给当前的Activity,这会调用Activity的dispatchTouchEvent()方法,具体的事件处理交给Activity的PhoneWindow来完成的,然后PhoneWindow再把事件的处理工作交给DecorView,之后再由DecorView将事件处理工作交给根ViewGroup。
5. View的工作流程
指的就是measure、layout、draw,其中,measure用来测量View的宽度和高,layout用来确定View的位置,draw则用来绘制View。