上一期我们讲到制作一个切图效果,就是在一组图片下放着相应的导航栏,我们的图片切换的时候我们的导航栏会跟随变化,还记得我们是怎么实现的吗,这一期我们来复习一下,顺便给大家介绍一个轮播图的效果,先给大家提个醒,关于轮播图呢,我们需要还用到定时器和线程的知识,而在我们的安卓中,我们使用线程或者定时器更新ul的时候会出现一个线程更新的错误;
出现这个错误,同时呢程序就崩溃了:
那么这个问题怎么解决呢?我卖个关子,需要用到handler,大家可以先去查一查,好了,那么废话不多说,我们开始吧;
1、创建好布局,我们可以自行到网上下载图片,取一个带有1~10序号的名字,当然你们也可以自己随便取多少张,不过后面的数组大小和Textview就要随之改变:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyIntentActivity2">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击滑动图片"
android:textSize="30sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="30dp"
android:gravity="center"
android:background="@drawable/titleshape"
android:id="@+id/tv_title2"/>
<ImageView
android:layout_width="400dp"
android:layout_height="500dp"
app:layout_constraintTop_toBottomOf="@+id/tv_title2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="20dp"
android:src="@drawable/mv1"
android:id="@+id/img_images"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/img_images"
android:orientation="horizontal"
android:layout_marginTop="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_1"
android:text="1"
android:background="@drawable/indexshape"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_2"
android:text="2"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_3"
android:text="3"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_4"
android:text="4"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_5"
android:text="5"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_6"
android:text="6"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_7"
android:text="7"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_8"
android:text="8"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_9"
android:text="9"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:id="@+id/tv_10"
android:text="10"
android:background="@drawable/indexshape2"
android:textSize="20sp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
布局代码有点多,但是有十个是重复的,毕竟我们下面的导航栏用的是Textview,所以我们需要有多少图片创多少个Textview;
先来分析一下布局,我们外面使用的是约束布局,一个提示标题,一个imageview,下面嵌套一个线性布局,方向为水平方向,将是个Textview放在嵌套的线性布局下面并且都取号id,当然,越有规律越好,这样做程序简单一点;其他的个性化的属性,比如大小颜色的话就看自己喜好了;还有我们可以看到我们的背景使用了drawable下面的资源文件,其实是我们自定义的形状:
android:background="@drawable/indexshape"
可能我这里比较卡,所以反应不是很灵敏,没关系,来看看我们的形状 ,我们新建形状文件在2、2、drawable文件下面:
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="oval"
xmlns:android="http://schemas.android.com/apk/res/android" >
<size android:width="25dp" android:height="25dp"/>
<solid android:color="#00ff00"/>
<stroke android:color="#ff0000" android:width="1dp"/>
</shape>
这个形状是我们选中的时候显示的,也就是绿色的那个;
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="oval"
xmlns:android="http://schemas.android.com/apk/res/android" >
<size android:width="25dp" android:height="25dp"/>
<solid android:color="#c0c0c0"/>
<stroke android:color="#00ff00" android:width="1dp"/>
</shape>
"#c0c0c0"这个颜色其实是一个灰色,也就是我们没有选中时候的形状
3、现在来分析一下我们如何实现的:
我们创建数组来存放导航图标,然后在初始化函数里面根据id加进去;
还记得之前我们怎么实现的滑动效果吗?
//1.4 添加监听函数
public boolean onTouchEvent(MotionEvent event){
opreate = true;
//1.5 记录下鼠标的开始和结束坐标
if(event.getAction() == MotionEvent.ACTION_DOWN){
startx = event.getX();
}else if(event.getAction() == MotionEvent.ACTION_UP){
endx = event.getX();
//1.6 开始判断是下一张还是上一张
if(startx - endx > 40){
if(index < 10){
index++;
}else{
index=1;
}
}else {
if(endx - startx > 40){
if(index>1){
index--;
}else{
index=10;
}
}
}
}
//设置图片并且更换背景
setBackground();
opreate = false;
return false;
}
创建三个变量,一个是鼠标的开始x坐标,一个是结束x坐标,
我们记录下鼠标的位置之后就进行判断,如果是左滑就index++,并且调用一下设置图片的函数,当然右滑也一样:
//1.4 添加监听函数
public boolean onTouchEvent(MotionEvent event){
opreate = true;
//1.5 记录下鼠标的开始和结束坐标
if(event.getAction() == MotionEvent.ACTION_DOWN){
startx = event.getX();
}else if(event.getAction() == MotionEvent.ACTION_UP){
endx = event.getX();
//1.6 开始判断是下一张还是上一张
if(startx - endx > 40){
if(index < 10){
index++;
}else{
index=1;
}
}else {
if(endx - startx > 40){
if(index>1){
index--;
}else{
index=10;
}
}
}
}
//设置图片并且更换背景
setBackground();
opreate = false;
return false;
}
设置图片的函数也是自定义的,后面会讲到,opreate也是,我们用来判断操作的,来停止轮播效果的;
还有一个就是图片的索引,还记得之
//1.1 定义一个数组用来存放Textview
TextView[] tv = new TextView[11];
//定义鼠标的位置
float startx,endx;
//定义图片索引
int index = 1;
//获取图片对象
ImageView img_images;
前我们给图片取得名字吗,我们要求有规律,我这里是1~10,所以我们可以使用这个索引来拿到我们想要的图片并且展示:
//1.7 设置背景图片
private void setBackground(){
//1.8 获取资源对象
Resources resources = getResources();
// 拿到图片id
int id = resources.getIdentifier(getPackageName()+":"+"drawable/mv"+index,null,null);
img_images.setImageResource(id);
// 同时切换当前导航图标背景
for (int i = 1; i < 11; i++) {
if(index == i){
tv[i].setBackgroundResource(R.drawable.indexshape);
}else{
tv[i].setBackgroundResource(R.drawable.indexshape2);
}
}
}
我们又创建了一个专门设置图片的放大,这里实现了一个利用图片的索引点亮下面导航栏的效果,所以可以看到图片对应导航栏图片的滚动效果;这里可以看到那个数组,我们是如何初始化这个是个导航图标的数组的?
//1.2 初始化变量
private void init_patams(){
//1.3 将所有图片对象加入hash表
tv[1]=findViewById(R.id.tv_1);
tv[2]=findViewById(R.id.tv_2);
tv[3]=findViewById(R.id.tv_3);
tv[4]=findViewById(R.id.tv_4);
tv[5]=findViewById(R.id.tv_5);
tv[6]=findViewById(R.id.tv_6);
tv[7]=findViewById(R.id.tv_7);
tv[8]=findViewById(R.id.tv_8);
tv[9]=findViewById(R.id.tv_9);
tv[10]=findViewById(R.id.tv_10);
//获取图片对象
img_images = findViewById(R.id.img_images);
//为对象添加单击响应监听器
for (int i = 1; i < 11; i++) {
tv[i].setOnClickListener(this);
}
}
在我们自己定义的初始化变量的函数里面,一个个添加的,我们是从1开始加到10的,这就是我们初始化的时候为什么要设置数组长度为11的原因,都是为了和下表对应起来;
4、定义好了变量和切换图片的函数并且写好了监听方法之后我们的滑动实现切图效果就完成了,接下来我们实现一个点击导航图标进行图片跳转的效果;
首先我们需要给10个导航图标添加监听方法,也就会我们的数组元素,这就在初始化函数里面就用到一个简单的for循环,而且是1~10,由于导航图标有10个,数量比较多,我们可以使用之前一期介绍监听方法的时候介绍的另一种监听方法,实现监听接口,然后在run方法里使用switch吧:
public class MyIntentActivity2 extends AppCompatActivity implements View.OnClickListener,Runnable
我们在主类上实现一下;
//2.1 编写鼠标单击导航图标的响应
@Override
public void onClick(View view) {
opreate = true;
switch (view.getId()){
case R.id.tv_1:
index = 1;
break;
case R.id.tv_2:
index = 2;
break;
case R.id.tv_3:
index = 3;
break;
case R.id.tv_4:
index = 4;
break;
case R.id.tv_5:
index = 5;
break;
case R.id.tv_6:
index = 6;
break;
case R.id.tv_7:
index = 7;
break;
case R.id.tv_8:
index = 8;
break;
case R.id.tv_9:
index = 9;
break;
case R.id.tv_10:
index = 10;
break;
default:
break;
}
//设置图片并且更换背景
setBackground();
opreate = false;
}
当然10个图标的话只需要改变响应的索引就可以,最后根据这个索引调用我们的设置图片的函数就可以了;
到了这里,我们就可以实现点击导航图标实现图片切换的功能了;
5、那么接下来就来到我们真正的主题了,如何实现轮播图?
其实也挺简单,不过要解决线程不安全的问题,我们可以在线程里面调用handler对象的post()方法,这个方法的参数是一个Runnable接口方法,我们在这个接口的run方法操作我们的ul更新就可以了,比如我们要切换图片,我们可以新建一个线程,然后在线程里面进行索引的加加,也就是index++;
为了简单我们可以让主类实现Runnable接口,然后重写run方法:
//2.2 因为Java支持实现多接口,要实现共享的数据我们就将主类实现一下Runnable接口
@Override
public void run() {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//2.3 下面是一个同步代码块,一般加载会出现线程抢占的地方;
//原理:我只会让一个线程进入这里
//2.4 我们使用同步代码块来将索引进行2s的加加,在大于10的时候让索引为1
synchronized (this) {
if (index > 10) {
index = 1;
}
//3.2 我们在使用的进行操作的时候不进行更新
if(!opreate){
handler.post(new Runnable() {
@Override
public void run() {
setBackground();
index++;
}
});
}
}
}
}, 0, 2000);
}
这就是实现轮播图的主体,开启一个定时器,然后还用了同步代码块,其实在这里用不用无所谓,因为我们就一个子线程,然后使用判断语句防止index超过10;
当然,我们判断了一下opreate,这个其实是我们自定义的一个bool类型的数据,之前在监听函数里见到过,看这里的逻辑,如果这个值为flase那么我们就会对index++,而且会使用handler对象的post方法来对组件进行更新操作,这个handler必须要用,我这里的意思就是将子线程切换到了ul线程,所以我们就可以对ul更新了;
这个handler对象定义很简单,定义为全局就可以:
//3.1 由于线程的不安全性,我们无法直接去改变ul,所以通过创建一个hander来间接操作
Handler handler = new Handler();
//2.7 创建一个判断是否处于操作状态
boolean opreate= false;
然后在oncreate方法里面开启线程就可以:
//初始化对象
init_patams();
//2.5 现在定义一个线程开启,让index自己变化
Thread thread = new Thread(this);
thread.start();
不过不要忘了要放在初始化函数的后面;
好了,这一期就到这了,学会了吗?快去试试吧;