博主前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住也分享一下给大家,
👉点击跳转到网站
一、介绍如何实现小球的往复运动,实现原理
1、View 类定义了一组 invalidate()方法,该方法有好几个版本:
- public void invalidate()
- public void invalidate(int l, int t, int r, int b)
- public void invalidate(Rect dirty)
invalidate()用于重绘组件,不带参数表示重绘整个视图区域,带参数表示重绘指定的区域。
如果要去追溯该方法的源码,大概就是将重绘请求一级级往上交到 ViewRoot,调用 ViewRoot的 scheduleTraversals()方法重新发起重绘请求,scheduleTraversals()方法会发送一个异步消息,调用 performTraversals()方法执行重绘,而 performTraversals()方法最终调用 onDraw()方法。所以,简单来说,调用 View 的 invalidate()方法就相当于调用了 onDraw()方法,
而 onDraw()方法中就是我们编写的绘图代码。
如果要刷新组件或者让画面动起来,我们只需调用 invalidate()方法即可。通过改变数据来影响绘制结果,这是实现组件刷新或实现动画的基本思路。
invalidate()方法只能在 UI 线程中调用,如果是在子线程中刷新组件,View 类还定义了另一组名为 postInvalidate 的方法:
- public void postInvalidate()
- public void postInvalidate(int left, int top, int right, int bottom)
二、接下来我们就创建自定义View实现小球的往复运动
1、首先创建自定义View类BallMoveView
/**
* 球往复运动
*/
public class BallMoveView extends View {
//小球的水平位置
private int x;
//小球的垂直位置,固定为100
private static final int Y = 100;
//小球的半径
private static final int RADIUS = 30;
//小球的颜色
private static final int COLOR = Color.RED;
//声明画笔对象
private Paint paint;
//移动的方向
private boolean direction;
//该构造函数会在代码里面new的时候调用
//当不需要使用xml声明或者不需要使用inflate动态加载的时候,实现此构造函数即可
public BallMoveView(Context context) {
super(context);
}
//在布局layout中使用时调用
//在布局文件中定义了该组件,则会调用此构造方法来创建对象
// 当需要在xml中声明此控件,则需要实现此构造函数。
// 并且在构造函数中把自定义的属性与控件的数据成员连接起来。
public BallMoveView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//初始化画笔,参数表示抗锯齿
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(COLOR);
x = RADIUS;
}
//接受一个style资源
public BallMoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//根据x,y的坐标值画一个小球
canvas.drawCircle(x, Y, RADIUS, paint);
//改变 x 坐标的值,调用 invalidate()方法后,
//小球将因 x 的值发生改变而产生移动的效果
int width = this.getMeasuredWidth();//获取组件的宽度
//小球的水平位置 x 值小于等于小球的半径,说明小球已到达左边边
//界
if (x <= RADIUS) {
direction = true;
}
//小球的水平位置 x 值大于等于组件的宽度减去小球的半径,
// 说明小球已到达右边边界
if (x >= width - RADIUS) {
direction = false;
}
x = direction ? x + 5 : x - 5;
}
}
2、在对应的activity_view1.xml布局文件中引用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".View1Activity">
<com.example.alertdialog.view.BallMoveView
android:id="@+id/ballMoveView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
3、在 View1Activity 中恰恰是通过定时器周期性调用了 invalidate()方法不断重绘组件,也就是不断调用 onDraw()方法,因为小球的位置由 x 来决定,onDraw()每调用一次,x 的值就会变化一次,小球绘制的位置自然也会跟着一起改变,最后形成了小球移动的效果。
具体代码如下:
public class View1Activity extends AppCompatActivity {
private BallMoveView ballMoveView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view1);
ballMoveView = findViewById(R.id.ballMoveView);
tv = findViewById(R.id.tv);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
ballMoveView.postInvalidate();
}
},0,50); //参数二表示:任务执行前的延迟毫秒数,参数三表示:连续任务执行间的时间为50毫秒
}
}
注意:
上面代码中,通过 Timer 类定义一个计时器,延时 0 毫秒开始计时,每隔 50 毫秒计时一次。
定时任务类 TimerTask 其实就是一个子线程,所以,不能使用只能运行在 UI 线程中的invalidate()方法而只能调用 postInvalidate()方法来重绘组件。
具体效果如下: