现在的位置: 首页 > 移动开发 > 正文

Android开发中ViewFlipper和ViewSwitcher使用GestureDetector完成切换

2019年09月05日 移动开发 ⁄ 共 12312字 ⁄ 字号 评论关闭

原文地址:http://codetheory.in/android-viewflipper-and-viewswitcher/

本文翻译地非常粗糙,原因有二:其一是我英文水平太差,其二是我对Android平台开发还不够熟悉;最后实在没耐心了,翻译出来的内容就只求自己明白,如果您因此有一定困惑,请谅解,阅读原文可能会更有帮助。


当开发Android应用时,我们大多数人都会遇到这样一个需求,在应用中整合多个视图之间切换的功能;因此它应该显示一个视图,可以是ImageView或者包含在LinearLayout、ReleativeLayout等容器内的集合,每一次通过手势可以滑动显示下一个或上一个视图;想像一下图片幻灯片或者分步的电子商务结账步骤。

基本上当你想在一组相关视图中进行切换并且同时只显示其中一个视图时,你可以使用Android给我们提供的ViewFlipper或者ViewSwithcer组件。手势的监测主要通过MotionEvent类来实现,但本文我们使用GestureDetector来完成。

ViewFlipper

当我们想在两个或更多视图之间进行切换时,ViewFlipper类是最容易被关注到的,下面我们尝试使用它创建一个ImageView幻灯片。

首先定义一个布局文件res/layout/activity_gesture.xml:

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:id="@+id/relativeLayout">
 
 
    <ViewFlipper
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/viewFlipper"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">
 
    </ViewFlipper>
 
</RelativeLayout>

很明显有两种方式向ViewFlipper中添加子视图:

  • 在上面的布局文件中添加
  • 编程方式添加

在本示例中我们采用第二种方式;在一切开始之前我们还需要准备几张图片放到res/drawable/ 目录,我从LoremPixel上下载了6张400x600的图片并放到drawable目录中,将它们分别命名为first.png、second.png、third.png等。(图片自行准备,比如我从百度上下载了6张美女图片偷笑

接下来需要做的就是以编程的方式将图片添加到ViewFlipper中,如下所示(Activity的onCreate()方法):

private ViewFlipper mViewFlipper;
private GestureDetector mGestureDetector;
 
int[] resources = {
        R.drawable.first,
        R.drawable.second,
        R.drawable.third,
        R.drawable.fourth,
        R.drawable.fifth,
        R.drawable.sixth
};
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_gesture);
 
    // Get the ViewFlipper
    mViewFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
 
    // Add all the images to the ViewFlipper
    for (int i = 0; i < resources.length; i++) {
        ImageView imageView = new ImageView(this);
        imageView.setImageResource(resources[i]);
        mViewFlipper.addView(imageView);
    }
}

现在如果你在模拟器或实体机上运行程序,你会发现只能看到第一张图片。如果你试着在屏幕上滑动手指/鼠标,什么也不会发生;因此接下来我们将使用GestureDetector类集成手势监测功能,为了做到这些我们首先实现自定义的手势监测类作为Activity的内部类:

class CustomGestureDetector extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
 
        // Swipe left (next)
        if (e1.getX() > e2.getX()) {
            mViewFlipper.showNext();
        }
 
        // Swipe right (previous)
        if (e1.getX() < e2.getX()) {
            mViewFlipper.showPrevious();
        }
 
        return super.onFling(e1, e2, velocityX, velocityY);
    }
}

超级容易理解的代码,如果触摸的起始X坐标比最终的X坐标大那么手指是向左滑动,也就是说用户的意图是看下一张图片(调用ViewFlipper的showNext()方法);如果恰恰相反则需要调用ViewFlipper对象的showPrevious() 方法(查看上一张图片)。

最后一步是初始化自定义手势监测类,并在Activity的 onTouchEvent() 方法中将所有触摸事件发送给它。

初始化代码需要放到onCreate()方法中:

CustomGestureDetector customGestureDetector = new CustomGestureDetector();
mGestureDetector = new GestureDetector(this, customGestureDetector);

onTouchEvent() 方法实现如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
 
    return super.onTouchEvent(event);
}

在如果你现在在设备上测试代码,你会发现视图切换正常工作了!恭喜你使用ViewFlipper完成了第一个幻灯片!但是现在有个问题是在视图切换过程中没有动画,这让用户体验非常糟糕;因此接下来我们使用Android内置的动画资源为ViewFlipper切换添加上基本的渐入渐出动画;我们仅需要做的是在 onCreate() 方法中添加下面一段代码:

// Set in/out flipping animations
mViewFlipper.setInAnimation(this, android.R.anim.fade_in);
mViewFlipper.setOutAnimation(this, android.R.anim.fade_out);

噢!现在在图片视图切换时产生了一些有趣的转变;现在我们完整的 Activity 类代码如下所示:

public class GestureActivity extends Activity {
 
    private ViewFlipper mViewFlipper;
    private GestureDetector mGestureDetector;
 
    int[] resources = {
            R.drawable.first,
            R.drawable.second,
            R.drawable.third,
            R.drawable.fourth,
            R.drawable.fifth,
            R.drawable.sixth
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gesture);
 
        // Get the ViewFlipper
        mViewFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
 
        // Add all the images to the ViewFlipper
        for (int i = 0; i < resources.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(resources[i]);
            mViewFlipper.addView(imageView);
        }
 
        // Set in/out flipping animations
        mViewFlipper.setInAnimation(this, android.R.anim.fade_in);
        mViewFlipper.setOutAnimation(this, android.R.anim.fade_out);
 
        CustomGestureDetector customGestureDetector = new CustomGestureDetector();
        mGestureDetector = new GestureDetector(this, customGestureDetector);
    }
 
    class CustomGestureDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
 
            // Swipe left (next)
            if (e1.getX() > e2.getX()) {
                mViewFlipper.showNext();
            }
 
            // Swipe right (previous)
            if (e1.getX() < e2.getX()) {
                mViewFlipper.showPrevious();
            }
 
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
 
        return super.onTouchEvent(event);
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.gesture, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

自动开始切换

如果你想要自开始切换的功能,ViewFlipper 给我们提供了实现该功能的接口,在 onCreate() 方法只需要下面两行代码:

mViewFlipper.setAutoStart(true);
mViewFlipper.setFlipInterval(2000); // flip every 2 seconds (2000ms)

设置setAutoStart()为true会在ViewFlipper连接(attach)到视图窗口时自动调用startFilpping()方法;setFlipInterval()方法定义视图切换的间隔时间(单位为毫秒)。

你可能还想添加一个播放按钮来触发幻灯片,如果那样你只需要给按钮设置一个单击事件监听器调用startFilpping()方法,同时停止按钮调用stopFlipping()方法。

自定义视图切换动画

我们也可以编写自己的XML动画资源文件用来代替Android内置的动画,并将其应用到 ViewFlipper 视图切换上;接下来看一下怎样编写一个简单的动画用于图片的滑入滑出。这里我们定义4个动画资源文件:

  • 向左滑动时,下一个图片进入屏幕(left_in.xml),同时当前图片从左边移出(left_out.xml)
  • 向右滑动时,前一个图片进入屏幕(right_in.xml),同时当前图片从右边移出(right_out.xml)

res/anim/left_in.xml 文件代码:

<?xml version="1.0" encoding="utf-8"?>
 
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="500"
        android:fromXDelta="100%p"
        android:toXDelta="0" />
 
    <alpha
        android:duration="500"
        android:fromAlpha="0.1"
        android:toAlpha="1.0" />
 
</set>

res/anim/left_out.xml 文件代码:

<?xml version="1.0" encoding="utf-8"?>
 
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="500"
        android:fromXDelta="0"
        android:toXDelta="-100%p" />
 
    <alpha
        android:duration="500"
        android:fromAlpha="1.0"
        android:toAlpha="0.1" />
</set>

res/anim/right_in.xml 文件代码:

<?xml version="1.0" encoding="utf-8"?>
 
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="500"
        android:fromXDelta="-100%p"
        android:toXDelta="0" />
 
    <alpha
        android:duration="500"
        android:fromAlpha="0.1"
        android:toAlpha="1.0" />
 
</set>

res/anim/right_out.xml 文件代码:

<?xml version="1.0" encoding="utf-8"?>
 
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="500"
        android:fromXDelta="0"
        android:toXDelta="100%p" />
 
    <alpha
        android:duration="500"
        android:fromAlpha="1.0"
        android:toAlpha="0.1" />
 
</set>

接下来在Activity的onCreate()方法中移除 setInAnimation() 和 setOutAnimation() 方法调用代码;我们在自定义手势监测类的 onFling() 方法的if代码块中调用它们,设置新的动画资源后的自定义手势监测类代码如下所示:

class CustomGestureDetector extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
 
        // Swipe left (next)
        if (e1.getX() > e2.getX()) {
            mViewFlipper.setInAnimation(GestureActivity.this, R.anim.left_in);
            mViewFlipper.setOutAnimation(GestureActivity.this, R.anim.left_out);
 
            mViewFlipper.showNext();
        }
 
        // Swipe right (previous)
        if (e1.getX() < e2.getX()) {
            mViewFlipper.setInAnimation(GestureActivity.this, R.anim.right_in);
            mViewFlipper.setOutAnimation(GestureActivity.this, R.anim.right_out);
 
            mViewFlipper.showPrevious();
        }
 
        return super.onFling(e1, e2, velocityX, velocityY);
    }
}

在你的设备或模拟器中测试一下,你会喜欢在图片幻灯片中切换时漂亮的滑动效果。

除了使用手势在视图之间切换,你也可以使用按钮的单击事件监听器来触发 mViewFlipper.showNext() 和 mViewFlipper.showPrevious()  动作。

ViewSwitcher

现在如果你已经正确理解了 ViewFlipper 那么 ViewSwithcer 对你来说就是小菜一碟了,跟 ViewFlipper 一样 ViewSwithcer 也是一种 ViewAnimator(他们都是ViewAnimator 的子类),但 ViewSwithcer
只能拥有两个子视图(它们之间只有这一个重大的区别)且同时只能显示一个,我们可以像给 ViewFlipper 添加子视图一样给 ViewSwithcer 添加子视图,另外还可以使用它的工厂创建视图。

如果我像上面的例子一样添加子视图:

// Get the ViewFlipper
mViewSwitcher = (ViewSwitcher) findViewById(R.id.viewSwitcher);
 
// Add all the images to the ViewFlipper
for (int i = 0; i < resources.length; i++) {
    ImageView imageView = new ImageView(this);
    imageView.setImageResource(resources[i]);
    mViewSwitcher.addView(imageView);
}

这段代码在resources数组只有两个元素时正常运行,否则(数据元素多于2个)它将抛出一个异常:java.lang.IllegalStateException: Can't add more than 2 views to a ViewSwitcher。

我们可以调用 ViewSwithcer 的 setFactory() 方法来设置创建两个views的工厂来代替两次调用 addView() 方法(尽管我只是喜欢调用addView() 两次)。

mViewSwitcher.setFactory(new ViewSwitcher.ViewFactory() {
    @Override
    public View makeView() {
        ImageView imageView = new ImageView(GestureActivity.this);
        imageView.setImageResource(resources[0]);
 
        // Log.d(TAG, "setFactory#makeView");
 
        return imageView;
    }
});

makeView()方法会被调用两次最终生成两个view,这种情况下它使用相同的图片资源;因此我们需要设置一些变量(也许基于mViewSwitcher.getChildCount())来确保第二个view选择其它的图片。

这里是Activity使用ViewSwithcer的示例(与前面使用ViewFlipper的例子非常相似):

public class GestureActivity extends Activity {
 
    // private ViewFlipper mViewFlipper;
    private ViewSwitcher mViewSwitcher;
    private GestureDetector mGestureDetector;
 
    int[] resources = {
            R.drawable.first,
            R.drawable.second/*,
            R.drawable.third,
            R.drawable.fourth,
            R.drawable.fifth,
            R.drawable.sixth*/
    };
 
    private String TAG = GestureActivity.class.getSimpleName();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gesture);
 
        // Get the ViewFlipper
        mViewSwitcher = (ViewSwitcher) findViewById(R.id.viewSwitcher);
 
        /*mViewSwitcher.setFactory(new ViewSwitcher.ViewFactory() {
            @Override
            public View makeView() {
                ImageView imageView = new ImageView(GestureActivity.this);
                imageView.setImageResource(resources[0]);
 
                Log.d(TAG, "setFactory#makeView");
 
                return imageView;
            }
        });*/
 
        // Add all the images to the ViewFlipper
        for (int i = 0; i < resources.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(resources[i]);
            mViewSwitcher.addView(imageView);
        }
 
        // Set in/out flipping animations
        //mViewFlipper.setInAnimation(this, android.R.anim.fade_in);
        //mViewFlipper.setOutAnimation(this, android.R.anim.fadeout);
 
        CustomGestureDetector customGestureDetector = new CustomGestureDetector();
        mGestureDetector = new GestureDetector(this, customGestureDetector);
    }
 
    class CustomGestureDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
 
            // Swipe left (next)
            if (e1.getX() > e2.getX()) {
                mViewSwitcher.setInAnimation(GestureActivity.this, R.anim.left_in);
                mViewSwitcher.setOutAnimation(GestureActivity.this, R.anim.left_out);
 
                mViewSwitcher.showNext();
            }
 
            // Swipe right (previous)
            if (e1.getX() < e2.getX()) {
                mViewSwitcher.setInAnimation(GestureActivity.this, R.anim.right_in);
                mViewSwitcher.setOutAnimation(GestureActivity.this, R.anim.right_out);
 
                mViewSwitcher.showPrevious();
            }
 
            /*mViewSwitcher.getInAnimation().setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
 
                }
 
                @Override
                public void onAnimationEnd(Animation animation) {
                    Log.d(TAG, "Animation End");
                    Log.d(TAG, "Child Count: " + mViewSwitcher.getChildCount());
                }
 
                @Override
                public void onAnimationRepeat(Animation animation) {
 
                }
            });*/
 
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
 
        return super.onTouchEvent(event);
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.gesture, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

我能立刻想到使用 ViewSwithcer 的场景是你需要登录和注册表单,或者当登录提交按钮被点击时,第二个视图显示出一个加载器或一些确认信息。

ViewFlipper和ViewSwitcher 的区别

我是在一个类似于图像幻灯片应用中遇到的这两个类,当时我需要完成一个视图切换到另一视图的特定需求。现在基于我们前面了解的知识可以认识到,这两个类之间唯一的区别就是 ViewSwithcer 只支持两个子视图而 ViewFlipper 可以拥有多个,另外 ViewSwithcer 可以通过 setFactory() 方法设置生产视图的工厂。

很多情况下我们可能更倾向于使用ViewFlipper而非ViewSwithcer。

结论

当一个特定的需求需要在不同视图之间切换时这些小部件是相当有用的,尽管在我的需求中我最终使用的是ViewPager,因为我真正需要的滑动风格是一旦当前图片拉向左边时下一张图片要紧紧跟随,在到达某一阈值前你释放了图片(或视图)那么它会回到原来的状态,并且下一张图片(或视图)会移出屏幕的右边缘。

如果你对这些视图动画有什么问题,请在评论部分随意讨论。

抱歉!评论已关闭.