不同于java的多线程,安卓提供了一套自己的多线程操作。主要是通过Handler类和Message类来完成。天天吃外卖,突然看到美团外卖的listview上有这么个可以自动切换的广告,于是就做这么个效果。(虽然我不明白为什么它是按照1-3-2的顺序的。。。。。。)
一、分析一下结构
这里用到的ViewPager组件就不多讲了,详细的参考
鱼鱼Chen之学写自己的apk(一) ViewPager实现第一次使用的引导
http://blog.csdn.net/zerolovesc1993/article/details/45066993
MyTransformer是一个自定义的类,满足接口ViewPager.PageTransformer
二、关于ViewPager的切换动画
这个在之前没有提到(因为鄙人的疏忽)。这个是ViewPager中实现切换动画的方法,只要在主类中加一句
viewPager.setPageTransformer(true, new MyTransformer());即可
然后是我们的自定义类
public class MyTransformer implements ViewPager.PageTransformer {
private final static float MIN_SCALE = 0.75f;
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE + (1 - MIN_SCALE)
* (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
这边我是直接复制了一个Google提供好的。动画类不是很熟悉,没怎么写过。但是几个属性还是可以提一下的
Scale是缩放,Translation是位移,Alpha是透明度。这些都很好理解,关于float position这个变量我想提一提。
[-1,1]其实就是你滑动时,显示的两个图片,右边为(0,1],左边为[-1,0),而左边的完全不可视的就是<-1。同理,右边的不可视的为>1
举个例子,如果此时2显示着,我们右滑,2对应的就是[-1,0),而3对应的是(0,1]。1,4分别对应<-1,>1的情况。此时float position随右滑变大。反之,左滑的同理,只不过float position随左滑变小。
三、引入Handler类
首先,简单讲一下知识点。因为纯粹是个人的理解,所以讲的可能不够专业化,但是肯定很形象~~最好是再百度将理论补全。首先,你的所有耗时操作都应该新建一个子线程去执行,放在主线程里会造成ANR(应用无响应),我相信大家的手机都出过这个情况。不过,请记住,在支线是不能更新UI的。比如,你在子线程里写textview.settext("");是会报错的,因为这是不安全的(google规定的,虽然我不懂为什么不安全)。所以要想通过子线程来更新UI怎么办呢?
主线程提供一个Handler类,接受Message类的对象,通过辨识对象的what属性来选择行为。而你需要耗时的行为,放在子线程中执行,完成后发送一个对应what的消息即可。还有一个就是Message发送后会放在一个looper里(消息队列)handler会按照顺序处理
好,简单提了一下相关的知识点,让我们来看一下代码。这边为了方便使用,我直接使用了匿名类。
先上一些声明
private int index = 0;
public Handler mHandler;
private Thread mThread;
private final static int START_UPDATA = 1;
以下主线程的代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
initItem();
initDoc();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case START_UPDATA:
viewPager.setCurrentItem(index);
break;
default:
break;
}
}
};
}
接着是自定义的子线程
class ChangeThread extends Thread implements Runnable {
@Override
public void run() {
super.run();
synchronized (MainActivity.class) {
try {
Log.i("Tips", "开始了!");
do {
Thread.sleep(2000);
index++;
if (index == 3) {
index = 0;
}
Message message = new Message();
message.what = START_UPDATA;
mHandler.sendMessage(message);
} while (MainActivity.ChangeThread.interrupted() == false);
} catch (Exception e) {
// TODO: handle exception
} finally {
Log.i("Tips", "结束了");
}
}
}
}
好了,我来解释一下。首先,我的作用是每隔2s切换ViewPager来实现循环播放。过2s就封装一个Message对象,并把what设为我们指定的值,并设置好index。接着我们在匿名类里重写handemessage方法,处理消息,接受到对应what消息时将viewpager的page设为指定index。
这边有个要注意的地方,我们循环的条件用(MainActivity.ChangeThread.interrupted() == false)是一种合理的选择,为什么呢?如果要停止线程,thread对象的stop是不安全的,用interrupt方法才是最佳,所以这边这么写。
写到这边自然还没有结束,我们还要启动线程呢!
mThread = new ChangeThread();
mThread.start();
写一个Button,把这两句放入就可以了!
四、思考
之前写自己的app的时候也就用到了这么多,当时可能就一带而过了吧。现在会用了就想到了许多别的问题,花了一些时间解决了。在这里也分享一下,希望给对遇到同样问题的朋友点参考。
1·、多个子线程开启
多次点击,发现刚刚写好的代码会有bug,切换速度在变快。很简单,每次点击都会有一个新的ChangeThread对象执行了!于是,想到了用synchronized,用来保证同时只有一个运行,剩下的会排队,直到前一个完成。
synchronized (MainActivity.class)
把子线程的方法封入其中即可,意思为一次只能让MainActivity类同时运行一个ChangeThread。一开始我尝试填入
synchronized (this),然后出现ANR了。仔细想了想,我也不确定对不对。前一种写法的话,thread会生成,只是去排队了。所以不会造成阻塞。而后一种写法,只有在前一个thread完成后,才开始生成,所以会阻塞!
2、停止多个线程
停止线程的话,直接用thread.interrupt即可。但是我这边又遇到了别的问题,当我多次点击开始,启动了多个线程对象时,没法关掉了。。。。。怎么办呢?我想了个办法。
新建一个list集合,放入线程对象
private List<Thread> threads;
每次thread.start时,将其放入集合。这样,多次点击,我只要遍历集合,并interrupt即可
添加部分:
mThread = new ChangeThread();
mThread.start();
threads.add(mThread);
去除部分:
if (threads.size() > 0) {
Thread currentThread = threads.get(0);
currentThread.interrupt();
threads.remove(currentThread);
稍微加了一个修改text的代码。完整的MainActivity如下:
public class MainActivity extends Activity implements OnPageChangeListener,
OnClickListener {
private ViewPager viewPager;
private List<View> picsList;
private List<Thread> threads;
private ImageView[] point;
private MyPageAdapter adapter;
private int currentIndex = 0;
private Button btnStart, btnEnd;
private int index = 0;
public Handler mHandler;
private Thread mThread;
private final static int START_UPDATA = 1;
private int[] pics = { R.drawable.no280, R.drawable.no281, R.drawable.no282 };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
initItem();
initDoc();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case START_UPDATA:
viewPager.setCurrentItem(index);
break;
default:
break;
}
}
};
}
private void init() {
viewPager = (ViewPager) findViewById(R.id.viewPager);
picsList = new ArrayList<View>();
threads = new ArrayList<Thread>();
adapter = new MyPageAdapter(picsList);
btnStart = (Button) findViewById(R.id.btnStart);
btnEnd = (Button) findViewById(R.id.btnEnd);
btnStart.setOnClickListener(this);
btnEnd.setOnClickListener(this);
}
private void initItem() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
for (int i = 0; i < pics.length; i++) {
ImageView imageView = new ImageView(this);
imageView.setLayoutParams(params);
imageView.setImageResource(pics[i]);
picsList.add(imageView);
}
viewPager.setAdapter(adapter);
viewPager.setPageTransformer(true, new MyTransformer());
viewPager.setOnPageChangeListener(this);
}
private void initDoc() {
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.linearLayout_point);
point = new ImageView[pics.length];
for (int i = 0; i < pics.length; i++) {
point[i] = (ImageView) linearLayout.getChildAt(i);
point[i].setEnabled(true);
}
point[currentIndex].setEnabled(false);
}
private void setcurrentDoc(int position) {
point[position].setEnabled(false);
point[currentIndex].setEnabled(true);
currentIndex = position;
}
@Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
@Override
public void onPageSelected(int position) {
setcurrentDoc(position);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnStart:
mThread = new ChangeThread();
mThread.start();
threads.add(mThread);
if (threads.size() > 1) {
btnEnd.setText("结束(还有" + (threads.size() - 1) + "条线程等待开始)");
} else {
btnEnd.setText("结束");
}
break;
case R.id.btnEnd:
if (threads.size() > 0) {
Thread currentThread = threads.get(0);
currentThread.interrupt();
threads.remove(currentThread);
if (threads.size() > 1) {
btnEnd.setText("结束(还有" + (threads.size() - 1) + "条线程等待开始)");
} else {
btnEnd.setText("结束");
}
}
break;
default:
break;
}
}
class ChangeThread extends Thread implements Runnable {
@Override
public void run() {
super.run();
synchronized (MainActivity.class) {
try {
Log.i("Tips", "开始了!");
do {
Thread.sleep(2000);
index++;
if (index == 3) {
index = 0;
}
Message message = new Message();
message.what = START_UPDATA;
mHandler.sendMessage(message);
} while (MainActivity.ChangeThread.interrupted() == false);
} catch (Exception e) {
// TODO: handle exception
} finally {
Log.i("Tips", "结束了");
}
}
}
}
}
老样子,百度云链接:http://pan.baidu.com/s/1bn96GUV