ViewFilpper 是Android官方提供的一个View容器类,继承于ViewAnimator类,用于实现页面切换,也可以设定时间间隔,让它自动播放。
又ViewAnimator继承至于FrameLayout的,所以ViewFilpper的Layout里面可以放置多个View,继承关系如下:
本示例通过ViewFlipper和GestureDetector.OnGestureListener实现自动播放和手势滑屏事件,先看效果:
Activity
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.GestureDetector;
- import android.view.MotionEvent;
- import android.view.ViewGroup.LayoutParams;
- import android.view.animation.Animation;
- import android.view.animation.AnimationUtils;
- import android.widget.ImageView;
- import android.widget.ViewFlipper;
- public class ViewFlipperActivity extends Activity implements android.view.GestureDetector.OnGestureListener {
- private int[] imgs = { R.drawable.img1, R.drawable.img2,
- R.drawable.img3, R.drawable.img4, R.drawable.img5 };
- private GestureDetector gestureDetector = null;
- private ViewFlipper viewFlipper = null;
- private Activity mActivity = null;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mActivity = this;
- viewFlipper = (ViewFlipper) findViewById(R.id.viewflipper);
- gestureDetector = new GestureDetector(this); // 声明检测手势事件
- for (int i = 0; i < imgs.length; i++) { // 添加图片源
- ImageView iv = new ImageView(this);
- iv.setImageResource(imgs[i]);
- iv.setScaleType(ImageView.ScaleType.FIT_XY);
- viewFlipper.addView(iv, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
- }
- viewFlipper.setAutoStart(true); // 设置自动播放功能(点击事件,前自动播放)
- viewFlipper.setFlipInterval(3000);
- if(viewFlipper.isAutoStart() && !viewFlipper.isFlipping()){
- viewFlipper.startFlipping();
- }
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- viewFlipper.stopFlipping(); // 点击事件后,停止自动播放
- viewFlipper.setAutoStart(false);
- return gestureDetector.onTouchEvent(event); // 注册手势事件
- }
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (e2.getX() - e1.getX() > 120) { // 从左向右滑动(左进右出)
- Animation rInAnim = AnimationUtils.loadAnimation(mActivity, R.anim.push_right_in); // 向右滑动左侧进入的渐变效果(alpha 0.1 -> 1.0)
- Animation rOutAnim = AnimationUtils.loadAnimation(mActivity, R.anim.push_right_out); // 向右滑动右侧滑出的渐变效果(alpha 1.0 -> 0.1)
- viewFlipper.setInAnimation(rInAnim);
- viewFlipper.setOutAnimation(rOutAnim);
- viewFlipper.showPrevious();
- return true;
- } else if (e2.getX() - e1.getX() < -120) { // 从右向左滑动(右进左出)
- Animation lInAnim = AnimationUtils.loadAnimation(mActivity, R.anim.push_left_in); // 向左滑动左侧进入的渐变效果(alpha 0.1 -> 1.0)
- Animation lOutAnim = AnimationUtils.loadAnimation(mActivity, R.anim.push_left_out); // 向左滑动右侧滑出的渐变效果(alpha 1.0 -> 0.1)
- viewFlipper.setInAnimation(lInAnim);
- viewFlipper.setOutAnimation(lOutAnim);
- viewFlipper.showNext();
- return true;
- }
- return true;
- }
- @Override
- public boolean onDown(MotionEvent e) {
- return false;
- }
- @Override
- public void onLongPress(MotionEvent e) {
- }
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- return false;
- }
- @Override
- public void onShowPress(MotionEvent e) {
- }
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return false;
- }
- }
main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <ViewFlipper
- android:id="@+id/viewflipper"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
- </LinearLayout>
示例分析
一、自动播放
ViewFlipper控件,是ImageView的容器,用于添加显示的图片资源,主要功能有两个:添加显示View和自动播放View
通过实现父类android.view.ViewGroup的addView(View child, ViewGroup.LayoutParams params)添加View资源,即图片和填充样式
启动自动播放View,可以通过设置如下三个成员函数实现:
1、 setAutoStart(true),设置是否自动播放功能,true为自动播放,false为不自动播放,开启自动播放设为true
2、 setFlipInterval(int milliseconds),设置View播放的时间间隔,如3000(3秒)
3、 startFlipping(),开始自动播放
停止自动播放View,设置成员函数如下:
1、 stopFlipping(),停止自动播放
2、 setAutoStart(false),停止自动播放,设为false
二、手势滑屏
手势滑动屏幕动画,是通过android.view.GestureDetector类检测各种手势事件实现的,该类有两个回调接口(Interface)
A、GestureDetector.OnDoubleTapListener,用来通知DoubleTap双击事件,类似于鼠标的双击事件,接口三个抽象回调函数如下
1、onDoubleTap(MotionEvent e):DoubleTap双击手势事件后通知(触发)
2、onDoubleTapEvent(MotionEvent e):DoubleTap双击手势事件之间通知(触发),包含down、up和move事件(这里指的是在双击之间发生的事件,例如在同一个地方双击会产生DoubleTap手势,而在DoubleTap手势里面还会发生down和up事件,这两个事件由该函数通知)
3、onSingleTapConfirmed(MotionEvent e):用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势;那么如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,此时触发的就是SingleTapConfirmed事件
B、GestureDetector.OnGestureListener,用来通知普通的手势事件(down、longPress、scroll、up等),接口具体的六个抽象回调函数如下
1、onDown(MotionEvent e):down事件,表示按下事件
2、onSingleTapUp(MotionEvent e):一次点击up事件,表示按下后的抬起事件
3、onShowPress(MotionEvent e):down事件发生而move或则up还没发生前触发该事件,此事件一般用于通知用户press按击事件已发生
4、onLongPress(MotionEvent e):长按事件,down事件后up事件前的一段时间间隔后(由系统分配,也可自定义),如果仍然按住屏幕则视为长按事件
5、onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY):滑动手势事件,例如scroll事件后突然up,fling的速度大小由e每秒x和y改变大小决定
6、onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY):在屏幕上拖动事件,即down按下点——scroll拖动——up抬起点的move移动事件
本示例的滑动屏幕动画,仅用到了上面的GestureDetector.OnGestureListener及其onFling事件,具体实现步骤如下:
1、Activity实现android.view.GestureDetector.OnGestureListener 监听接口,并声明gestureDetector = new GestureDetector(this); 用于监听手势事件
2、在Activity的成员函数onTouchEvent(MotionEvent event)中,注册GestureDetector.OnGestureListener手势监听的gestureDetector.onTouchEvent(event);事件接口
3、在GestureDetector.OnGestureListener回调函数onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 中,实现滑屏动画
三、屏幕渐变效果
1、 当手势从左向右滑动时,图片是左进右出
if (e2.getX() - e1.getX() > 120) ,即up终点(e2)与down起点(e1)的滑动距离大于120,来检测从左向右滑动事件
push_left_in.xml —— 左进渐变效果
- <?xml version="1.0" encoding="utf-8"?>
- <set xmlns:android="http://schemas.android.com/apk/res/android" >
- <translate
- android:duration="1500"
- android:fromXDelta="100%p"
- android:toXDelta="0" />
- <alpha
- android:duration="1500"
- android:fromAlpha="0.1"
- android:toAlpha="1.0" />
- </set>
push_left_out.xml —— 右出渐变效果
- <?xml version="1.0" encoding="utf-8"?>
- <set xmlns:android="http://schemas.android.com/apk/res/android" >
- <translate
- android:duration="1500"
- android:fromXDelta="0"
- android:toXDelta="-100%p" />
- <alpha
- android:duration="1500"
- android:fromAlpha="1.0"
- android:toAlpha="0.1" />
- </set>
2、 当手势从右向左滑动时,图片是右进左出
if (e2.getX() - e1.getX() < -120) ,即up终点(e2)与down起点(e1)的滑动距离小于-120,来检测从右向左滑动事件
push_right_in.xml —— 右进渐变效果
- <?xml version="1.0" encoding="utf-8"?>
- <set xmlns:android="http://schemas.android.com/apk/res/android" >
- <translate
- android:duration="1500"
- android:fromXDelta="-100%p"
- android:toXDelta="0" />
- <alpha
- android:duration="1500"
- android:fromAlpha="0.1"
- android:toAlpha="1.0" />
- </set>
push_right_out.xml —— 右出渐变效果
- <?xml version="1.0" encoding="utf-8"?>
- <set xmlns:android="http://schemas.android.com/apk/res/android" >
- <translate
- android:duration="1500"
- android:fromXDelta="0"
- android:toXDelta="100%p" />
- <alpha
- android:duration="1500"
- android:fromAlpha="1.0"
- android:toAlpha="0.1" />
- </set>
其中,android:duration表示渐变持续时间;translate表示位移变换;alpha表示透明度变换
translate
android:fromXDelta="-100%p" android:toXDelta="0" 表示图片从左进入,从不可见到可见
android:fromXDelta="0" android:toXDelta="100%p" 表示图片从右滑出,从可见到不可见
alpha
android:fromAlpha="1.0" android:toAlpha="0.1" 表示图片从不透明(1.0)到透明(0.1)
android:fromAlpha="0.1" android:toAlpha="1.0" 表示图片从透明(0.1)到不透明(1.0)
推荐参考:
GestureDetector.OnDoubleTapListener
GestureDetector.OnGestureListener
Gallery 是Android官方提供的一个View容器类,继承于AbsSpinner类,用于实现页面滑动效果。
从上面的继承关系可以看出,AbsSpinner类继承自AdapterView,因此我们可以自定义实现Adapter,来填充Gallery容器的数据。
本示例通过自己实现一个Adapter,来填充Gallery容器的图片数据,首先看效果:
Activity
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.Gallery;
- import android.widget.Toast;
- public class GalleryActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- Gallery gallery = (Gallery) findViewById(R.id.gallery);
- gallery.setAdapter(new ImageAdapter(this)); // gallery添加ImageAdapter图片资源
- gallery.setOnItemClickListener(listener); // gallery设置点击图片资源的事件
- }
- AdapterView.OnItemClickListener listener = new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- Toast.makeText(GalleryActivity.this, "图片 " + (position + 1), Toast.LENGTH_SHORT).show();
- }
- };
- }
ImageAdapter.java
- import android.content.Context;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.Gallery;
- import android.widget.ImageView;
- public class ImageAdapter extends BaseAdapter {
- private Context mContext;
- // 图片数组源
- private Integer[] imgs = { R.drawable.img1, R.drawable.img2,
- R.drawable.img3, R.drawable.img4, R.drawable.img5,
- R.drawable.img6, R.drawable.img7};
- public ImageAdapter(Context c) {
- mContext = c;
- }
- @Override
- public int getCount() {
- return imgs.length;
- }
- // 获取图片位置
- @Override
- public Object getItem(int position) {
- return imgs[position];
- }
- // 获取图片ID
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ImageView imageview = new ImageView(mContext);
- imageview.setImageResource(imgs[position]);
- imageview.setLayoutParams(new Gallery.LayoutParams(240, 120)); // 设置布局 图片120×120显示
- imageview.setScaleType(ImageView.ScaleType.CENTER); // 设置显示比例类型(不缩放)
- return imageview;
- }
- }
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <!-- 文字居中显示 android:gravity -->
- <!-- 控件居中显示 android:layout_gravity -->
- <TextView
- android:id="@+id/tv"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:layout_gravity="center"
- android:layout_marginTop="50dip"
- android:textColor="#ffff0000"
- android:textSize="30sp"
- android:text="Gallery Test"/>
- <Gallery
- android:id="@+id/gallery"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dip"
- android:layout_below="@id/tv" />
- </RelativeLayout>
示例分析:
1、ImageAdapter中,是继承BaseAdapter自定义实现的,重载了BaseAdapter的几个虚方法,其中重要的有两个
getCount() : 返回资源的大小(总长度)
getView(int position, View convertView, ViewGroup parent): 返回当前显示的资源(获取焦点)
2、GalleryActivity中,通过gallery.setAdapter(imgAdapter); 设置资源,然后设置gallery的点击监听事件。
3、main.xml中,是布局文件,显示一个TextView和Gallery,RelativeLayout是相对布局
总体来说,Gallery 上述的示例很简单,结构比较清晰,能够满足基本的应用。
Gallery 高级应用
上面的示例,仅仅是最简单的Gallery应用,如果我们想做的更酷、更炫的效果:
1、Gallery 图片显示能够循环播放,即向右滑到左侧第一张图片后,需要接着显示最后一张图片;向左滑到最后一张图片后,需要接着显示第一张图片,往复不间断显示的循环效果。
2、选中图片高亮,未选中图片阴影,更加突出当前获取焦点的选中图片
3、区分“点击”与“选中”图片事件的区别和适用场景
效果1 —— 高亮显示
没有选中,在GalleryActivity中,设置gallery.setUnselectedAlpha(0.3f); 透明度为0.3
选中,在ImageAdapter的getView(int position, View convertView, ViewGroup parent)中,设置imageview.setBackgroundColor(Color.alpha(1)); 背景色为1
效果2 —— 循环播放
原理:Gallery循环播放的原理,跟循环链表的思想一样,首尾item连接都是通过“取余”实现
修改1、ImageAdapter中的getCount() 方法中,修改返回值为无穷大 return Integer.MAX_VALUE;
修改2、ImageAdapter中的getView(int position, View convertView, ViewGroup parent)方法中,设置imageview.setImageResource(imgs[position % imgs.length]); 取余
修改3、GalleryActivity中,设置gallery.setSelection(imgAdapter.imgs.length * 100); 使gallery显示图片的位置从中间开始显示(即imgAdapter.imgs.length * 100)
修改解释:
修改1,主要是为了是循环接近无限往复循环,使position无限大,循环在实践应用上不容易结束(理论上会结束,即2^31-1约20亿次循环后)
修改2,通过取余,使图片能够重复利用并显示
修改3,由于起始位置如果是0,则向右滑动左侧将无法循环(此时左侧将为-1,超出了imgs[]数组的下边界),因此开始应设置起始位置为imgAdapter.imgs.length的整数倍
效果3 —— “点击”和“选中”事件
1、点击事件OnItemClickListener,是需要用手点击才触发,滑动时不触发
2、选中事件OnItemSelectedListener,是当图片滑到屏幕正中,则视为自动选中,在滑动的过程中会触发
适用场景:
1、点击事件OnItemClickListener,是在确定要选中该项时,才点击进行逻辑处理
2、选中事件OnItemSelectedListener,可以用来提醒用户,当前获取焦点的项,如果确认为该项则需要点击OnItemClickListener后,进行下一步的逻辑处理
高级应用完整代码:
Activity
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.Gravity;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.Gallery;
- import android.widget.Toast;
- public class GalleryActivity extends Activity {
- private ImageAdapter imgAdapter = null; // 声明图片资源对象
- private Gallery gallery = null;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- gallery = (Gallery) findViewById(R.id.gallery);
- imgAdapter = new ImageAdapter(this);
- gallery.setAdapter(imgAdapter); // 设置图片资源
- gallery.setGravity(Gravity.CENTER_HORIZONTAL); // 设置水平居中显示
- gallery.setSelection(imgAdapter.imgs.length * 100); // 设置起始图片显示位置(可以用来制作gallery循环显示效果)
- gallery.setOnItemClickListener(clickListener); // 设置点击图片的监听事件(需要用手点击才触发,滑动时不触发)
- gallery.setOnItemSelectedListener(selectedListener); // 设置选中图片的监听事件(当图片滑到屏幕正中,则视为自动选中)
- gallery.setUnselectedAlpha(0.3f); // 设置未选中图片的透明度
- gallery.setSpacing(40); // 设置图片之间的间距
- }
- // 点击图片的监听事件
- AdapterView.OnItemClickListener clickListener = new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- Toast.makeText(GalleryActivity.this, "点击图片 " + (position + 1), 100).show();
- }
- };
- // 选中图片的监听事件
- AdapterView.OnItemSelectedListener selectedListener = new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- Toast.makeText(GalleryActivity.this, "选中图片 " + (position + 1), 20).show();
- }
- @Override
- public void onNothingSelected(AdapterView<?> arg0) {
- }
- };
- }
ImageAdapter.java
- import android.content.Context;
- import android.graphics.Color;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.Gallery;
- import android.widget.ImageView;
- public class ImageAdapter extends BaseAdapter {
- private Context mContext;
- // 图片数组源
- public Integer[] imgs = { R.drawable.img1, R.drawable.img2,
- R.drawable.img3, R.drawable.img4, R.drawable.img5,
- R.drawable.img6, R.drawable.img7};
- public ImageAdapter(Context c) {
- mContext = c;
- }
- @Override
- public int getCount() {
- return Integer.MAX_VALUE;
- }
- // 获取图片位置
- @Override
- public Object getItem(int position) {
- return imgs[position];
- }
- // 获取图片ID
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ImageView imageview = new ImageView(mContext);
- imageview.setImageResource(imgs[position % imgs.length]);
- imageview.setLayoutParams(new Gallery.LayoutParams(200, 94)); // 设置布局 图片120×120显示
- imageview.setScaleType(ImageView.ScaleType.CENTER); // 设置显示比例类型
- imageview.setBackgroundColor(Color.alpha(1));
- return imageview;
- }
- }
参考推荐:
Android系统自带一个Gallery浏览图片的应用,通过手指拖动时能够非常流畅的显示图片,用户交互和体验都很好。
本示例就是通过Gallery和自定义的View,模仿实现一个仿Gallery图像集的图片浏览效果。效果图如下:
1、基本原理
在 Activity 中实现 OnGestureListener 的接口 onFling() 手势事件,通过自定义的 View 绘制draw() 图片
2、Activity
Activity中,通过onTouchEvent() 注册 myGesture.onTouchEvent(event)
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- flingView.onFling(0); // 手指抬起后,重置滑动距离offsetX = 0
- break;
- }
- return myGesture.onTouchEvent(event);
- }
接着实现接口OnGestureListener 的 onScroll()方法,给继承自View的 FlingView 的handleScroll()成员方法传递滑动参数,获取滑动的x轴距离
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- flingView.handleScroll(-1 * (int) distanceX);
- return true;
- }
接着实现接口OnGestureListener 的 OnFling()方法,给继承自View的 FlingView 的onFling()成员方法传递滑动参数,获取手势的速度
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- flingView.onFling((int) - velocityX);
- return true;
- }
3、FlingView
FlingView中,获取来自Activity中的手势速度
- public void onFling(int paramFloat1) {
- if (offsetX > GalleryDemoActivity.deviceScreenWidth / 5) {
- if (fBitmap != null) {
- isFling = true;
- isFlingRight = true;
- }
- } else if (offsetX < -GalleryDemoActivity.deviceScreenWidth / 5) {
- if (nBitmap != null) {
- isFling = true;
- isFlingLeft = true;
- }
- }
- // 开始动画效果
- startAnimation(new MyAnimation());
- }
在滑动过程中,通过实现View的Draw()方法绘制图片,注意:此时需要同时绘制当前图片(获取焦点)和下一张图片(即将获取焦点)共两张图片
- @Override
- public void draw(Canvas canvas) {
- Paint paint = new Paint();
- Rect rect = new Rect();
- canvas.drawColor(Color.BLACK);
- // 绘制当前图片
- if (bitmap != null) {
- int left = offsetX;
- int top = offsetY;
- int right = offsetX + GalleryDemoActivity.deviceScreenWidth;
- int bottom = offsetY + GalleryDemoActivity.deviceScreenHeight;
- rect.set(left, top, right, bottom);
- canvas.drawBitmap(bitmap, null, rect, paint);
- }
- // 绘制下一张图片
- if (offsetX < 0) { // 向左滑动
- if (nBitmap != null) {
- int left = GalleryDemoActivity.deviceScreenWidth + 15 + offsetX;
- int top = 0;
- int right = left + GalleryDemoActivity.deviceScreenWidth;
- int bottom = GalleryDemoActivity.deviceScreenHeight;
- rect.set(left, top, right, bottom);
- canvas.drawBitmap(nBitmap, null, rect, paint);
- }
- } else if (offsetX > 0) { // 向右滑动
- if (fBitmap != null) {
- int left = -GalleryDemoActivity.deviceScreenWidth - 15 + offsetX;
- int top = 0;
- int right = left + GalleryDemoActivity.deviceScreenWidth;
- int bottom = GalleryDemoActivity.deviceScreenHeight;
- rect.set(left, top, right, bottom);
- canvas.drawBitmap(fBitmap, null, rect, paint);
- }
- }
- }
- @Override
- protected void onAnimationEnd() {
- if (isFlingRight) { // 向右滑动,position减1
- nBitmap = bitmap;
- bitmap = fBitmap;
- fBitmap = null;
- postion = postion - 1;
- } else if (isFlingLeft) { // 向左滑动,position加1
- fBitmap = bitmap;
- bitmap = nBitmap;
- nBitmap = null;
- postion = postion + 1;
- }
- isFlingRight = false;
- isFlingLeft = false;
- isFling = false;
- offsetX = 0;
- if (fBitmap == null && offsetX == 0) { // 如果前一张图片为空(向右滑),则重置前一张图片(position - 1)
- if (postion > 0) {
- fBitmap = getBitmap(postion - 1);
- }
- } else if (nBitmap == null && offsetX == 0) { // 如果后一张图片为空(向左滑),则重置后一张图片(position + 1)
- if (postion < bitmaps.length - 1) {
- nBitmap = getBitmap(postion + 1);
- }
- }
- clearAnimation();
- }
4、手势坐标介绍
本示例中,用到了OnGestureListener接口的onScroll()和OnFling()方法,涉及到了Android系统坐标及触摸MotionEvent e1和e2、速度velocityX、velocityY等值
Android屏幕坐标系如下图(左)
(1)MotionEvent中 e1是手指第一次按上屏幕的起点,e2是抬起手指离开屏幕的终点,根据上图Android屏幕坐标系可知:
手指向右滑动,终点(e2)在起点(e1)的右侧,有e2.getX() - e1.getX() 大于0
手指向左滑动,终点(e2)在起点(e1)的左侧,有e2.getX() - e1.getX() 小于0
手指向下滑动,终点(e2)在起点(e1)的下侧,有e2.getY() - e1.getY() 大于0
手指向上滑动,终点(e2)在起点(e1)的上侧,有e2.getY() - e1.getY() 小于0
(2)onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
distanceX,是前后两次call的X距离,不是e2与e1的水平距离
distanceX,是前后两次call的Y距离,不是e2与e1的垂直距离
具体数值的方向,请详见上图(中)
(3)onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
velocityX,是X轴的每秒速度
velocityY,是Y轴的每秒速度
具体数值的方向,请详见上图(右)
仔细观察可以发现:velocityX、velocityY的方向与distanceX、distanceY方向正好相反
更多OnGestureListener接口函数介绍,请见上一篇博客 Android 滑动效果入门篇(一)—— ViewFlipper
Android系统自带一个GridView和Gallery两个控件,GridView网格显示,Gallery单个浏览,两者结合起来可以真正实现Gallery浏览图片效果。
本示例通过GridView和Gallery两个控件,模仿实现一个完整的仿Gallery图像集的图片浏览效果。效果图如下:
1、GridView
首先,自定义一个GridImageAdapter图片适配器,用于填充GridView控件的图片
- public class GridImageAdapter extends BaseAdapter {
- private Context mContext;
- Drawable btnDrawable;
- public GridImageAdapter(Context context) {
- mContext = context;
- Resources resources = context.getResources();
- btnDrawable = resources.getDrawable(R.drawable.bg);
- }
- @Override
- public int getCount() {
- return ImageSource.mThumbIds.length;
- }
- @Override
- public Object getItem(int position) {
- return position;
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ImageViewExt imageView;
- int space;
- if (convertView == null) {
- imageView = new ImageViewExt(mContext);
- if (imageCol == 5) {
- space = dm.heightPixels / imageCol - 6;
- imageView.setLayoutParams(new GridView.LayoutParams(space, space));
- } else {
- space = dm.widthPixels / imageCol - 6;
- imageView.setLayoutParams(new GridView.LayoutParams( space, space));
- }
- imageView.setAdjustViewBounds(true);
- imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); // 缩放图片使其长和宽一样
- imageView.setPadding(3, 3, 3, 3);
- } else {
- imageView = (ImageViewExt) convertView;
- }
- imageView.setImageResource(ImageSource.mThumbIds[position]);
- return imageView;
- }
- }
- gridView = (GridView) findViewById(R.id.myGrid);
- gridImageAdapter = new GridImageAdapter(this);
- gridView.setAdapter(gridImageAdapter);
- gridView.setOnItemClickListener(listener); // 设置点击监听事件
最后,设置GridView控件的点击监听事件
- AdapterView.OnItemClickListener listener = new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
- Intent intent = new Intent();
- intent.setClass(GridViewActivity.this, GalleryActivity.class);
- intent.putExtra("position", position);
- startActivity(intent);
- }
- };
2、Gallery
完成了GridView的图片显示、监听事件后,现在点击图片,会启动一个Activity来显示当前点击的图片,此时显示图片的控件便是Gallery
首先,同GridView一样,自定义一个ImageAdapter图片适配器,用来填充Gallery
- public class ImageAdapter extends BaseAdapter {
- private Context mContext;
- private int mPos;
- public ImageAdapter(Context context) {
- mContext = context;
- }
- public void setOwnposition(int ownposition) {
- this.mPos = ownposition;
- }
- public int getOwnposition() {
- return mPos;
- }
- @Override
- public int getCount() {
- return ImageSource.mThumbIds.length;
- }
- @Override
- public Object getItem(int position) {
- mPos=position;
- return position;
- }
- @Override
- public long getItemId(int position) {
- mPos=position;
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- mPos=position;
- ImageView imageview = new ImageView(mContext);
- imageview.setBackgroundColor(0xFF000000);
- imageview.setScaleType(ImageView.ScaleType.FIT_CENTER);
- imageview.setLayoutParams(new myGallery.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- imageview.setImageResource(ImageSource.mThumbIds[position]);
- return imageview;
- }
- }
然后,用ImageAdapter填充Gallery
- myGallery galllery = (myGallery) findViewById(R.id.mygallery);
- Intent intent = getIntent();
- position = intent.getIntExtra("position", 0); // 获取GridViewActivity传来的图片位置position
- ImageAdapter imgAdapter=new ImageAdapter(this);
- galllery.setAdapter(imgAdapter); // 设置图片ImageAdapter
- galllery.setSelection(position); // 设置当前显示图片
- Animation an= AnimationUtils.loadAnimation(this,R.anim.scale ); // Gallery动画
- galllery.setAnimation(an);
此时,如果细心可以注意到,我们的Gallery也是自己定义的myGallery,具体定义如下:
- public class myGallery extends Gallery {
- boolean isFirst = false;
- boolean isLast = false;
- public myGallery(Context context) {
- super(context);
- }
- public myGallery(Context context, AttributeSet paramAttributeSet) {
- super(context, paramAttributeSet);
- }
- /** 是否向左滑动(true - 向左滑动; false - 向右滑动) */
- private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2) {
- return e2.getX() > e1.getX();
- }
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- ImageAdapter ia = (ImageAdapter) this.getAdapter();
- int p = ia.getOwnposition(); // 获取当前图片的position
- int count = ia.getCount(); // 获取全部图片的总数count
- int kEvent;
- if (isScrollingLeft(e1, e2)) {
- if (p == 0 && isFirst) {
- Toast.makeText(this.getContext(), "已是第一页", Toast.LENGTH_SHORT).show();
- } else if (p == 0) {
- isFirst = true;
- } else {
- isLast = false;
- }
- kEvent = KeyEvent.KEYCODE_DPAD_LEFT;
- } else {
- if (p == count - 1 && isLast) {
- Toast.makeText(this.getContext(), "已到最后一页", Toast.LENGTH_SHORT).show();
- } else if (p == count - 1) {
- isLast = true;
- } else {
- isFirst = false;
- }
- kEvent = KeyEvent.KEYCODE_DPAD_RIGHT;
- }
- onKeyDown(kEvent, null);
- return true;
- }
- }
GalleryActivity的布局文件gallery.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="horizontal"
- android:padding="10dip" >
- <RelativeLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#000000"
- android:padding="2dip" >
- <com.homer.gridgallery.myGallery
- android:id="@+id/mygallery"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:spacing="16dp" />
- </RelativeLayout>
- </LinearLayout>
参考推荐:
前面介绍了利用Android自带的控件,进行滑动翻页制作效果,现在我们通过代码实现一些滑动翻页的动画效果。
Animation实现动画有两个方式:帧动画(frame-by-frame animation)和补间动画(tweened animation)
本示例通过继承Animation自定义Rotate3D,实现3D翻页效果。效果图如下:
1、Rotate3D(Animation)
首先,自定义Animation的3D动画类Rotate3D
- public class Rotate3D extends Animation {
- private float fromDegree; // 旋转起始角度
- private float toDegree; // 旋转终止角度
- private float mCenterX; // 旋转中心x
- private float mCenterY; // 旋转中心y
- private Camera mCamera;
- public Rotate3D(float fromDegree, float toDegree, float centerX, float centerY) {
- this.fromDegree = fromDegree;
- this.toDegree = toDegree;
- this.mCenterX = centerX;
- this.mCenterY = centerY;
- }
- @Override
- public void initialize(int width, int height, int parentWidth, int parentHeight) {
- super.initialize(width, height, parentWidth, parentHeight);
- mCamera = new Camera();
- }
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- final float FromDegree = fromDegree;
- float degrees = FromDegree + (toDegree - fromDegree) * interpolatedTime; // 旋转角度(angle)
- final float centerX = mCenterX;
- final float centerY = mCenterY;
- final Matrix matrix = t.getMatrix();
- if (degrees <= -76.0f) {
- degrees = -90.0f;
- mCamera.save();
- mCamera.rotateY(degrees); // 旋转
- mCamera.getMatrix(matrix);
- mCamera.restore();
- } else if (degrees >= 76.0f) {
- degrees = 90.0f;
- mCamera.save();
- mCamera.rotateY(degrees);
- mCamera.getMatrix(matrix);
- mCamera.restore();
- } else {
- mCamera.save();
- mCamera.translate(0, 0, centerX); // 位移x
- mCamera.rotateY(degrees);
- mCamera.translate(0, 0, -centerX);
- mCamera.getMatrix(matrix);
- mCamera.restore();
- }
- matrix.preTranslate(-centerX, -centerY);
- matrix.postTranslate(centerX, centerY);
- }
- }
然后,实例化Rotate3D的旋转方向
- public void initAnimation() {
- // 获取旋转中心
- DisplayMetrics dm = new DisplayMetrics();
- dm = getResources().getDisplayMetrics();
- mCenterX = dm.widthPixels / 2;
- mCenterY = dm.heightPixels / 2;
- // 定义旋转方向
- int duration = 1000;
- lQuest1Animation = new Rotate3D(0, -90, mCenterX, mCenterY); // 下一页的【question1】旋转方向(从0度转到-90,参考系为水平方向为0度)
- lQuest1Animation.setFillAfter(true);
- lQuest1Animation.setDuration(duration);
- lQuest2Animation = new Rotate3D(90, 0, mCenterX, mCenterY); // 下一页的【question2】旋转方向(从90度转到0,参考系为水平方向为0度)(起始第一题)
- lQuest2Animation.setFillAfter(true);
- lQuest2Animation.setDuration(duration);
- rQuest1Animation = new Rotate3D(0, 90, mCenterX, mCenterY); // 上一页的【question1】旋转方向(从0度转到90,参考系为水平方向为0度)
- rQuest1Animation.setFillAfter(true);
- rQuest1Animation.setDuration(duration);
- rQuest2Animation = new Rotate3D(-90, 0, mCenterX, mCenterY); // 上一页的【question2】旋转方向(从-90度转到0,参考系为水平方向为0度)
- rQuest2Animation.setFillAfter(true);
- rQuest2Animation.setDuration(duration);
- }
2、Activity
首先,定义两个布局文件,用于旋转的画面切换
main.xml
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/layout_main"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- ...
- </LinearLayout>
next.xml
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/layout_next"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- ...
- </LinearLayout>
然后,初始化两个旋转的布局文件资源
- private void initMain(){
- setContentView(R.layout.main);
- layoutmain = (LinearLayout)findViewById(R.id.layout_main);
- btn_MainLast = (Button)findViewById(R.id.main_last);
- btn_MainNext = (Button)findViewById(R.id.main_next);
- btn_MainLast.setOnClickListener(listener);
- btn_MainNext.setOnClickListener(listener);
- }
- private void initNext(){
- setContentView(R.layout.next);
- layoutnext = (LinearLayout)findViewById(R.id.layout_next);
- btn_NextLast = (Button)findViewById(R.id.next_last);
- btn_NextNext = (Button)findViewById(R.id.next_next);
- btn_NextLast.setOnClickListener(listener);
- btn_NextNext.setOnClickListener(listener);
- }
最后,设置布局文件中的按钮监听事件,响应3D旋转动画和方向
- private View.OnClickListener listener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.main_last: // 上一页
- layoutmain.startAnimation(lQuest1Animation); // 当前页向左旋转(0,-90)
- initNext();
- layoutnext.startAnimation(lQuest2Animation); // 下一页向左旋转(90, 0)
- break;
- case R.id.main_next: // 下一页
- layoutmain.startAnimation(rQuest1Animation); // 当前页向右旋转(0,90)
- initNext();
- layoutnext.startAnimation(rQuest2Animation); // 下一页向右旋转(-90, 0)
- break;
- case R.id.next_last:
- layoutnext.startAnimation(lQuest1Animation);
- initMain();
- layoutmain.startAnimation(lQuest2Animation);
- break;
- case R.id.next_next:
- layoutnext.startAnimation(rQuest1Animation);
- initMain();
- layoutmain.startAnimation(rQuest2Animation);
- break;
- }
- }
- };
参考推荐:
上篇介绍了使用Animation实现3D动画旋转翻页效果,现在介绍图片倒影实现,先看效果图
本示例主要通过自定义Gallery和ImageAdapter(继承自BaseAdapter)实现
1、倒影绘制
ImageAdapter继承自BaseAdapter,详细实现可见 Android 滑动效果入门篇(二)—— Gallery 这里重点介绍倒影原理及实现
倒影原理:
倒影效果是主要由原图+间距+倒影三部分组成,高度大约为原图的3/2(原图为1、倒影为1/2)
原图,就是我们看到了最开始的图片
间距,是原图与倒影之间的间隙,如:reflectionGap = 4;
倒影,是原图下半部分1/2高度,通过矩阵变换matrix.preScale(1, -1); 获取倒立图片,然后再加上线性遮罩和阴影实现
倒影实现:
- /** 反射倒影 */
- public boolean createReflectedImages() {
- final int reflectionGap = 4;
- int index = 0;
- for (Map<String, Object> map : list) {
- Integer id = (Integer) map.get("image");
- Bitmap originalImage = BitmapFactory.decodeResource(mContext.getResources(), id); // 获取原始图片
- int width = originalImage.getWidth();
- int height = originalImage.getHeight();
- Matrix matrix = new Matrix();
- matrix.preScale(1, -1); // 图片矩阵变换(从低部向顶部的倒影)
- Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height/2, width, height/2, matrix, false); // 截取原图下半部分
- Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height / 2), Config.ARGB_8888); // 创建倒影图片(高度为原图3/2)
- Canvas canvas = new Canvas(bitmapWithReflection); // 绘制倒影图(原图 + 间距 + 倒影)
- canvas.drawBitmap(originalImage, 0, 0, null); // 绘制原图
- Paint paint = new Paint();
- canvas.drawRect(0, height, width, height + reflectionGap, paint); // 绘制原图与倒影的间距
- canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null); // 绘制倒影图
- paint = new Paint();
- LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0, bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
- paint.setShader(shader); // 线性渐变效果
- paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); // 倒影遮罩效果
- canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint); // 绘制倒影的阴影效果
- ImageView imageView = new ImageView(mContext);
- imageView.setImageBitmap(bitmapWithReflection); // 设置倒影图片
- imageView.setLayoutParams(new myGallery.LayoutParams(180, 240));
- imageView.setScaleType(ScaleType.MATRIX);
- mImages[index++] = imageView;
- }
- return true;
- }
2、myGallery
自定义Gallery来实现倒影图片的浏览与选择
- public class myGallery extends Gallery {
- private Camera mCamera = new Camera();
- private int mMaxRotationAngle = 60; // 最大旋转角度 60
- private int mMaxZoom = -120;
- private int mCoveflowCenter;
- public myGallery(Context context) {
- super(context);
- this.setStaticTransformationsEnabled(true);
- }
- public myGallery(Context context, AttributeSet attrs) {
- super(context, attrs);
- this.setStaticTransformationsEnabled(true);
- }
- public myGallery(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- this.setStaticTransformationsEnabled(true);
- }
- public int getMaxRotationAngle() {
- return mMaxRotationAngle;
- }
- public void setMaxRotationAngle(int maxRotationAngle) {
- mMaxRotationAngle = maxRotationAngle;
- }
- public int getMaxZoom() {
- return mMaxZoom;
- }
- public void setMaxZoom(int maxZoom) {
- mMaxZoom = maxZoom;
- }
- /** 获取Gallery的中心x */
- private int getCenterOfCoverflow() {
- return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
- }
- /** 获取View的中心x */
- private static int getCenterOfView(View view) {
- return view.getLeft() + view.getWidth() / 2;
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mCoveflowCenter = getCenterOfCoverflow();
- super.onSizeChanged(w, h, oldw, oldh);
- }
- @Override
- protected boolean getChildStaticTransformation(View child, Transformation trans) {
- final int childCenter = getCenterOfView(child);
- final int childWidth = child.getWidth();
- int rotationAngle = 0;
- trans.clear();
- trans.setTransformationType(Transformation.TYPE_BOTH); // alpha 和 matrix 都变换
- if (childCenter == mCoveflowCenter) { // 正中间的childView
- transformImageBitmap((ImageView) child, trans, 0);
- } else { // 两侧的childView
- rotationAngle = (int) ( ( (float) (mCoveflowCenter - childCenter) / childWidth ) * mMaxRotationAngle );
- if (Math.abs(rotationAngle) > mMaxRotationAngle) {
- rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle : mMaxRotationAngle;
- }
- transformImageBitmap((ImageView) child, trans, rotationAngle);
- }
- return true;
- }
- private void transformImageBitmap(ImageView child, Transformation trans, int rotationAngle) {
- mCamera.save();
- final Matrix imageMatrix = trans.getMatrix();
- final int imageHeight = child.getLayoutParams().height;
- final int imageWidth = child.getLayoutParams().width;
- final int rotation = Math.abs(rotationAngle);
- // 在Z轴上正向移动camera的视角,实际效果为放大图片; 如果在Y轴上移动,则图片上下移动; X轴上对应图片左右移动。
- mCamera.translate(0.0f, 0.0f, 100.0f);
- // As the angle of the view gets less, zoom in
- if (rotation < mMaxRotationAngle) {
- float zoomAmount = (float) (mMaxZoom + (rotation * 1.5));
- mCamera.translate(0.0f, 0.0f, zoomAmount);
- }
- mCamera.rotateY(rotationAngle); // rotationAngle 为正,沿y轴向内旋转; 为负,沿y轴向外旋转
- mCamera.getMatrix(imageMatrix);
- imageMatrix.preTranslate(-(imageWidth / 2), -(imageHeight / 2));
- imageMatrix.postTranslate((imageWidth / 2), (imageHeight / 2));
- mCamera.restore();
- }
- }
3、Activity
Activity中,主要实现自定义Gallery的图片填充ImageAdapter、myGallery选择事件监听、点击事件监听
- private void initRes(){
- tvTitle = (TextView) findViewById(R.id.tvTitle);
- gallery = (myGallery) findViewById(R.id.mygallery); // 获取自定义的myGallery控件
- adapter = new ImageAdapter(this);
- adapter.createReflectedImages(); // 创建倒影效果
- gallery.setAdapter(adapter);
- gallery.setOnItemSelectedListener(new OnItemSelectedListener() { // 设置选择事件监听
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- tvTitle.setText(adapter.titles[position]);
- }
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
- gallery.setOnItemClickListener(new OnItemClickListener() { // 设置点击事件监听
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- Toast.makeText(Main.this, "img " + (position+1) + " selected", Toast.LENGTH_SHORT).show();
- }
- });
- }
main.xml布局文件中,通过实现自定义的myGallery,来显示图片集合
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/tvTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:textSize="16sp" />
- <com.homer.reflect.myGallery
- android:id="@+id/mygallery"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/tvTitle"
- android:layout_marginTop="10dip" />
- </RelativeLayout>