这次学着做了一个可以左右触控来切换图片的DEMO,类似于漫画阅读器(布卡)的功能。
主要有下面两个技术点
1、GestureDetector类
主要是监听用户用手指在屏幕上的各种操作,然后根据操作来处理事件。
2、ViewFlipper控件
放滚动图片的容器,所有的功能也是基于此来实现的
看上去挺简单的,我要实现的是什么样的功能呢?
我把这个DEMO叫做漫画阅读器,打开应用,出来漫画的第一页,我手指对着屏幕由右向左划动,就切换到下一页漫画图片上。反之则是上一页。
OK,接下来就来实现。
老样子,先做页面布局。
activity_main.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"
tools:context=".MainActivity">
<ViewFlipper
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/viewFlipper"
android:layout_gravity="center_vertical" />
</LinearLayout>
在这里我创建了一个ViewFlipper的小控件。这是Android专门用来做屏幕滑动用的。
接下来就是代码实现
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewFlipper = (ViewFlipper)findViewById(R.id.viewFlipper);
viewFlipper.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean b = detector.onTouchEvent(event);
/*
这里监听的是手从屏幕上松开的事件。
因为flip事件感觉总是监听不到,需要在上面的detector.onTouchEvent没有监听到任何
事件时,重新做一下校验
*/
if(b == false){
if(event.getAction() == event.ACTION_UP){
//右向左划了一下,间距超过120像素了,切换图片
if(viewFlipper.getCurrentView().getX()< -120){
if(id<count) {
viewFlipper.showNext();
nextView = null;
}
//左向向划了一下,间距超过120像素了,切换图片
}else if(viewFlipper.getCurrentView().getX()> 120){
if(id>0) {
viewFlipper.showPrevious();
prevView = null;
}
}
//更新当前图片的坐标,免得在屏幕上显示不正确
viewFlipper.getCurrentView().setX(0);
//Log.d(TAG,"现在的坐标定在?"+viewFlipper.getCurrentView().getX());
}
}
return b;
}
});
在这里,我给viewFlipper分配了setOnTouchListener的事件,用来监听用户用手指在屏幕上的各种操作。不过很可惜。
看介绍说setOnTouchListener这个事件能做的事情并不多。
也无非就是按下,抬起之类的,不过我看源码里面好像蛮多的,
ACTION_UP
ACTION_CANCEL
ACTION_MOVE
ACTION_POINTER_UP
ACTION_POINTER_DOWN
等等……等等,有兴趣可以写代码一个一个试。而我这次决定用GestureDetector类来试一下
GestureDetector类是一个手势类,简单来就是可以识别很多的手势,所以我就用这个了。
从代码上看,GestureDetector类是要注入到OnTouchListener里面的。两者是相辅相成的,而不是说可以独立存在。
boolean b = detector.onTouchEvent(event);
就是创建的拦截方法,他截取了event事件,并且重新做解析。
然后完成以后,会返回true或false告诉你这次的事件是否处理完成。
这有什么用?用处就是万一他没处理好的话,我还可以用OnTouchListener再弥补一下
if(b==false)这一段就是在用户的手指离开屏幕后,我会再进行一次处理。前提是GestureDetector类没有很好的处理我的事件。
接下来就是detector这个方法的实现了。
GestureDetector类是一个抽象类,需要我去具体实现。
detector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
//Log.d(TAG,"onDown");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
// Log.d(TAG,"onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// Log.d(TAG,"onSingleTapUp");
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.d(TAG,"onScroll: e1="+e1.getX()+",e2="+e2.getX());
int nextId; //上一张图的索引
int prevId; //下一张图的索引
float offset = e1.getX()-e2.getX();
float x = viewFlipper.getX();
//当前正在显示的图片索引
id = viewFlipper.getDisplayedChild();
//如果已经到最后的话
if(id>=count && offset>0){
//最后一页了,所以不用再翻页
viewFlipper.getCurrentView().setX(x-offset);
}else if(id<=0 && offset<0) {
//第一页,不用再往前翻
viewFlipper.getCurrentView().setX(x-offset);
}else{
//其它情况
//重新定义当前图片的坐标,跟着手势走
viewFlipper.getCurrentView().setX(x - offset);
if(offset<0){ //手势是从左向右移
prevId = id-1; //取上一张图片的索引
prevView = viewFlipper.getChildAt(prevId); //上一张图
prevView.setVisibility(View.VISIBLE);
//紧贴前面一张图
prevView.setX(viewFlipper.getCurrentView().getX() - prevView.getWidth());
}else{ //手势是从右移向右的
nextId = id+1; //取下一张图片的索引
nextView = viewFlipper.getChildAt(nextId); //下一张图片
nextView.setVisibility(View.VISIBLE); //设置为可见
//紧贴前面一张图
nextView.setX(viewFlipper.getCurrentView().getX()+viewFlipper.getCurrentView().getWidth());
}
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// Log.d(TAG,"onLongPress");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(TAG,"onFling");
Log.i(TAG, "e1=" + e1.getX() + " e2=" + e2.getX() + " e1-e2=" + (e1.getX() - e2.getX()));
/*
两种情况:
一种是最后一页,并且是在做从右划到左的操作的时候
一种是第一页,并且是在做从左划到右的操作的时候
都不需要做图片切换,直接重置当前图片的坐标
*/
if((id>=count && (e1.getX() - e2.getX()>0)) || (id<= 0 && (e1.getX() - e2.getX()<0))){
viewFlipper.getCurrentView().setX(0);
return false;
}
//从右划到左的时候,只要超过120个象素就认为是成功的手势操作
if(e1.getX()-e2.getX()>120){
viewFlipper.showNext();
viewFlipper.getCurrentView().setX(0);
nextView = null;
return true;
}else if(e1.getX()-e2.getX()<-120){
//从左划到右
viewFlipper.showPrevious();
viewFlipper.getCurrentView().setX(0);
prevView = null;
return true;
}
//都不是,初始化图片的位置
viewFlipper.getCurrentView().setX(0);
if(nextView!=null) {
nextView.setVisibility(View.INVISIBLE); //设置为不可见
}
if(prevView!=null) {
prevView.setVisibility(View.INVISIBLE); //设置为不可见
}
return false;
}
});
这里共继承了它的6种事件
onDown:用户按下屏幕就会触发
onShowPress:非常短的时间内按下
onSingleTapUp:轻击一下屏幕
onScroll:手指在屏上划来划去
onLongPress:长按触摸屏
onFling:按下屏幕,划动后离开
嗯,这次我就用了两个事件onScroll和onFling
其它的……先不用了。
这里有要注间的就是return false,如果你要用onFling事件,你的onDown事件就设置成return true,不然的话他会直接不触发onFling事件的,我搞了老半天才弄清楚。
其它的具体逻辑代码可以看注释。
关于手指操作屏幕的处理我这就结束了。
接下来就是要把图片弄进去。
//detector = new GestureDetector(this);
//往viewFlipper添加View
viewFlipper.addView(getImageView(R.drawable.page_1));
viewFlipper.addView(getImageView(R.drawable.page_2));
viewFlipper.addView(getImageView(R.drawable.page_3));
viewFlipper.addView(getImageView(R.drawable.page_4));
viewFlipper.addView(getImageView(R.drawable.page_5));
viewFlipper.addView(getImageView(R.drawable.page_6));
count = viewFlipper.getChildCount()-1;
//禁止自动播放
viewFlipper.setAutoStart(false);
我一共弄了六张图片,做为一个测试。其实就是addView,把图片对象加到viewFlipper控件中去,超简单。
不过图片是要对象形式送进去的,所以我还得去加载一下图片资源
private ImageView getImageView(int id){
ImageView imageView = new ImageView(this);
Bitmap bitmap=readBitMap(MainActivity.this,id);
imageView.setImageBitmap(bitmap);
/*
注释掉的这段不行。实在是太慢了。
ImageView imageView = new ImageView(this);
imageView.setImageResource(id);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
*/
return imageView;
}
/**
* 以最省内存的方式读取本地资源的图片
* 这段函数网上抄的,确实比之前的setImageResource要快数倍
*
* @param context
* @param resId
* @return
*/
public static Bitmap readBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
//下面这两个函数过期了。所以不用了
//opt.inPurgeable = true;
//opt.inInputShareable = true;
// 获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
原先我用的是setImageResource的方法,而且网上很多也是用这个方法,但是不得不说。。。这个方法实在是太差了。没办法用,我六张图片一加载,卡成狗了。
所以我又在网上找了一下,用新的方法,readBitMap
顿时丝般软滑。。
然后……然后就全部完成了。
嗯。。虽然简单,但是其中的一些逻辑和用法也搞了我好久。
比如我看到网上很多用的是
public class MainActivity extends Activity implements OnTouchListener
来继承触摸方法,再声明
mGestureDetector = new GestureDetector(new gestureListener());
不过现在新的SDK里面已经不支持这样的用法了。
另外这只是提供一种思路,并不一定是对的,像图片资源的加载,如果真要做成在线阅读的形式,还要考虑到怎么样通过网络去获得图片,然后自动由程序来读取。
现在6张图片还可以,如果是60张,600张呢?在性能的优化上还是需要考虑的。
附上DEMO下载:
http://download.csdn.net/detail/gundamzaku/8857279
效果图: