一个模仿布卡那样的划动手势看在线漫画的简单应用DEMO

这次学着做了一个可以左右触控来切换图片的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


效果图:

    


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值