转载请声明出处
之前自己做的一个APP需要用到翻页阅读,网上看过立体翻页效果,不过bug太多了还不兼容。看了一下多看阅读翻页是采用平移翻页的,于是就仿写了一个平移翻页的控件。效果如下:
在翻页时页面右边缘绘制了阴影,效果还不错。要实现这种平移翻页控件并不难,只需要定义一个布局管理页面就可以了。具体实现上有以下难点:
1、循环翻页,页面的重复利用。
2、在翻页时过滤掉多点触碰。
3、采用setAdapter的方式设置页面布局和数据。
下面就来一一解决这几个难点。首先看循环翻页问题,怎么样能采用较少的页面实现这种翻页呢?由于屏幕上每次只能显示一张完整的页面,翻过去的页面也看不到,所以可以把翻过去的页面拿来重复利用,不必每次都new一个页面,所以,我只用了三张页面实现循环翻页。要想重复利用页面,首先要知道页面在布局中序号和对应的层次关系,比如一个父控件的子view的序号越大就位于越上层。循环利用页面的原理图如下:
向右翻页时状态图是这样的,只用了0、1、2三张页面,页面序号为2的位于最上层,我把它隐藏在左边,所以看到的只有页面1,页面0在1下面挡着也看不到,向右翻页时,页面2被滑到屏幕中,这时候把页面0的内容替换成页面2的前一页内容,把它放到之前页面2的位置,这时,状态又回到了初始状态,又可以继续向右翻页了!
向左翻页时是这样的,初始状态还是一样,当页面1被往左翻过时,看到的是页面0,这时候页面0下面已经没有页面了,而页面2已经用不到了,这时候把页面2放到页面0下面,这时候状态又回到了初始状态,就可以继续往左翻页了。
类似于这种循环效果的实现我一直用的解决方案都是将选中的置于最中间,比如原理图中的页面1,每次翻页完成后可见的都是页面1。在中也是同样的方案。这就解决了页面的重复利用问题了。
解决难点2 翻页时过滤多点触碰这个问题在中已经解决过了,就是用一个控制变量mEvents过滤掉pointer down或up后到来的第一个move事件。
解决难点3 采用adapter方式设置页面的布局和数据。这个在Android的AdapterView里用到的,但是我没有看它的adapter机制,太复杂了,我就搞了个简单的adapter,如下:
PageAdapter.java:
- package com.jingchen.pagerdemo;
-
- import android.view.View;
-
- public abstract class PageAdapter
- {
-
-
-
- public abstract View getView();
-
- public abstract int getCount();
-
-
-
-
-
-
-
-
-
- public abstract void addContent(View view, int position);
- }
这是一个抽象类,getView()用于返回页面的布局,getCount()返回数据总共需要多少页,addContent(View view, int position)这个是每翻过一页后将会被调用来请求页面数据的,参数view就是页面,position是表明第几页。待会儿会在自定义布局中定义setAdapter方法设置设配器。 OK,难点都解决了,自定义一个布局叫ScanView继承自RelativeLayout:
ScanView.java:
- package com.jingchen.pagerdemo;
-
- import java.util.Timer;
- import java.util.TimerTask;
-
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.LinearGradient;
- import android.graphics.Paint;
- import android.graphics.Paint.Style;
- import android.graphics.RectF;
- import android.graphics.Shader.TileMode;
- import android.os.Handler;
- import android.os.Message;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.View;
- import android.widget.RelativeLayout;
-
-
-
-
-
- public class ScanView extends RelativeLayout
- {
- public static final String TAG = "ScanView";
- private boolean isInit = true;
-
- private boolean isPreMoving = true, isCurrMoving = true;
-
- private int index;
- private float lastX;
-
- private int prePageLeft = 0, currPageLeft = 0, nextPageLeft = 0;
-
- private View prePage, currPage, nextPage;
-
- private static final int STATE_MOVE = 0;
- private static final int STATE_STOP = 1;
-
- private static final int PRE = 2;
- private static final int CURR = 3;
- private int state = STATE_STOP;
-
- private float right;
-
- private float moveLenght;
-
- private int mWidth, mHeight;
-
- private VelocityTracker vt;
-
- private float speed_shake = 20;
-
- private float speed;
- private Timer timer;
- private MyTimerTask mTask;
-
- public static final int MOVE_SPEED = 10;
-
- private PageAdapter adapter;
-
-
-
- private int mEvents;
-
- public void setAdapter(ScanViewAdapter adapter)
- {
- removeAllViews();
- this.adapter = adapter;
- prePage = adapter.getView();
- addView(prePage, 0, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- adapter.addContent(prePage, index - 1);
-
- currPage = adapter.getView();
- addView(currPage, 0, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- adapter.addContent(currPage, index);
-
- nextPage = adapter.getView();
- addView(nextPage, 0, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- adapter.addContent(nextPage, index + 1);
-
- }
-
-
-
-
-
-
- private void moveLeft(int which)
- {
- switch (which)
- {
- case PRE:
- prePageLeft -= MOVE_SPEED;
- if (prePageLeft < -mWidth)
- prePageLeft = -mWidth;
- right = mWidth + prePageLeft;
- break;
- case CURR:
- currPageLeft -= MOVE_SPEED;
- if (currPageLeft < -mWidth)
- currPageLeft = -mWidth;
- right = mWidth + currPageLeft;
- break;
- }
- }
-
-
-
-
-
-
- private void moveRight(int which)
- {
- switch (which)
- {
- case PRE:
- prePageLeft += MOVE_SPEED;
- if (prePageLeft > 0)
- prePageLeft = 0;
- right = mWidth + prePageLeft;
- break;
- case CURR:
- currPageLeft += MOVE_SPEED;
- if (currPageLeft > 0)
- currPageLeft = 0;
- right = mWidth + currPageLeft;
- break;
- }
- }
-
-
-
-
- private void addPrePage()
- {
- removeView(nextPage);
- addView(nextPage, -1, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
-
- adapter.addContent(nextPage, index - 1);
-
- View temp = nextPage;
- nextPage = currPage;
- currPage = prePage;
- prePage = temp;
- prePageLeft = -mWidth;
- }
-
-
-
-
- private void addNextPage()
- {
- removeView(prePage);
- addView(prePage, 0, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
-
- adapter.addContent(prePage, index + 1);
-
- View temp = currPage;
- currPage = nextPage;
- nextPage = prePage;
- prePage = temp;
- currPageLeft = 0;
- }
-
- Handler updateHandler = new Handler()
- {
-
- @Override
- public void handleMessage(Message msg)
- {
- if (state != STATE_MOVE)
- return;
-
-
- if (prePageLeft > -mWidth && speed <= 0)
- {
-
- moveLeft(PRE);
- } else if (currPageLeft < 0 && speed >= 0)
- {
-
- moveRight(CURR);
- } else if (speed < 0 && index < adapter.getCount())
- {
-
- moveLeft(CURR);
- if (currPageLeft == (-mWidth))
- {
- index++;
-
- addNextPage();
- }
- } else if (speed > 0 && index > 1)
- {
-
- moveRight(PRE);
- if (prePageLeft == 0)
- {
- index--;
-
- addPrePage();
- }
- }
- if (right == 0 || right == mWidth)
- {
- releaseMoving();
- state = STATE_STOP;
- quitMove();
- }
- ScanView.this.requestLayout();
- }
-
- };
-
- public ScanView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- init();
- }
-
- public ScanView(Context context)
- {
- super(context);
- init();
- }
-
- public ScanView(Context context, AttributeSet attrs)
- {
- super(context, attrs);
- init();
- }
-
-
-
-
- public void quitMove()
- {
- if (mTask != null)
- {
- mTask.cancel();
- mTask = null;
- }
- }
-
- private void init()
- {
- index = 1;
- timer = new Timer();
- mTask = new MyTimerTask(updateHandler);
- }
-
-
-
-
- private void releaseMoving()
- {
- isPreMoving = true;
- isCurrMoving = true;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event)
- {
- if (adapter != null)
- switch (event.getActionMasked())
- {
- case MotionEvent.ACTION_DOWN:
- lastX = event.getX();
- try
- {
- if (vt == null)
- {
- vt = VelocityTracker.obtain();
- } else
- {
- vt.clear();
- }
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- vt.addMovement(event);
- mEvents = 0;
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- mEvents = -1;
- break;
- case MotionEvent.ACTION_MOVE:
-
- quitMove();
- Log.d("index", "mEvents = " + mEvents + ", isPreMoving = "
- + isPreMoving + ", isCurrMoving = " + isCurrMoving);
- vt.addMovement(event);
- vt.computeCurrentVelocity(500);
- speed = vt.getXVelocity();
- moveLenght = event.getX() - lastX;
- if ((moveLenght > 0 || !isCurrMoving) && isPreMoving
- && mEvents == 0)
- {
- isPreMoving = true;
- isCurrMoving = false;
- if (index == 1)
- {
-
- state = STATE_MOVE;
- releaseMoving();
- } else
- {
-
- prePageLeft += (int) moveLenght;
-
- if (prePageLeft > 0)
- prePageLeft = 0;
- else if (prePageLeft < -mWidth)
- {
-
- prePageLeft = -mWidth;
- releaseMoving();
- }
- right = mWidth + prePageLeft;
- state = STATE_MOVE;
- }
- } else if ((moveLenght < 0 || !isPreMoving) && isCurrMoving
- && mEvents == 0)
- {
- isPreMoving = false;
- isCurrMoving = true;
- if (index == adapter.getCount())
- {
-
- state = STATE_STOP;
- releaseMoving();
- } else
- {
- currPageLeft += (int) moveLenght;
-
- if (currPageLeft < -mWidth)
- currPageLeft = -mWidth;
- else if (currPageLeft > 0)
- {
-
- currPageLeft = 0;
- releaseMoving();
- }
- right = mWidth + currPageLeft;
- state = STATE_MOVE;
- }
-
- } else
- mEvents = 0;
- lastX = event.getX();
- requestLayout();
- break;
- case MotionEvent.ACTION_UP:
- if (Math.abs(speed) < speed_shake)
- speed = 0;
- quitMove();
- mTask = new MyTimerTask(updateHandler);
- timer.schedule(mTask, 0, 5);
- try
- {
- vt.clear();
- vt.recycle();
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- break;
- default:
- break;
- }
- super.dispatchTouchEvent(event);
- return true;
- }
-
-
-
-
-
-
- @Override
- protected void dispatchDraw(Canvas canvas)
- {
- super.dispatchDraw(canvas);
- if (right == 0 || right == mWidth)
- return;
- RectF rectF = new RectF(right, 0, mWidth, mHeight);
- Paint paint = new Paint();
- paint.setAntiAlias(true);
- LinearGradient linearGradient = new LinearGradient(right, 0,
- right + 36, 0, 0xffbbbbbb, 0x00bbbbbb, TileMode.CLAMP);
- paint.setShader(linearGradient);
- paint.setStyle(Style.FILL);
- canvas.drawRect(rectF, paint);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mWidth = getMeasuredWidth();
- mHeight = getMeasuredHeight();
- if (isInit)
- {
-
- prePageLeft = -mWidth;
- currPageLeft = 0;
- nextPageLeft = 0;
- isInit = false;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b)
- {
- if (adapter == null)
- return;
- prePage.layout(prePageLeft, 0,
- prePageLeft + prePage.getMeasuredWidth(),
- prePage.getMeasuredHeight());
- currPage.layout(currPageLeft, 0,
- currPageLeft + currPage.getMeasuredWidth(),
- currPage.getMeasuredHeight());
- nextPage.layout(nextPageLeft, 0,
- nextPageLeft + nextPage.getMeasuredWidth(),
- nextPage.getMeasuredHeight());
- invalidate();
- }
-
- class MyTimerTask extends TimerTask
- {
- Handler handler;
-
- public MyTimerTask(Handler handler)
- {
- this.handler = handler;
- }
-
- @Override
- public void run()
- {
- handler.sendMessage(handler.obtainMessage());
- }
-
- }
- }
代码中的注释写的非常多,原理理解了看代码就容易看懂了。写完这个布局后再写一个ScanViewAdapter继承PageAdapter:
- package com.jingchen.pagerdemo;
-
- import java.util.List;
-
- import android.content.Context;
- import android.content.res.AssetManager;
- import android.graphics.Typeface;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.widget.TextView;
-
- public class ScanViewAdapter extends PageAdapter
- {
- Context context;
- List<String> items;
- AssetManager am;
-
- public ScanViewAdapter(Context context, List<String> items)
- {
- this.context = context;
- this.items = items;
- am = context.getAssets();
- }
-
- public void addContent(View view, int position)
- {
- TextView content = (TextView) view.findViewById(R.id.content);
- TextView tv = (TextView) view.findViewById(R.id.index);
- if ((position - 1) < 0 || (position - 1) >= getCount())
- return;
- content.setText(" 双峰叠障,过天风海雨,无边空碧。月姊年年应好在,玉阙琼宫愁寂。谁唤痴云,一杯未尽,夜气寒无色。碧城凝望,高楼缥缈西北。\n\n 肠断桂冷蟾孤,佳期如梦,又把阑干拍。雾鬓风虔相借问,浮世几回今夕。圆缺睛明,古今同恨,我更长为客。蝉娟明夜,尊前谁念南陌。");
- tv.setText(items.get(position - 1));
- }
-
- public int getCount()
- {
- return items.size();
- }
-
- public View getView()
- {
- View view = LayoutInflater.from(context).inflate(R.layout.page_layout,
- null);
- return view;
- }
- }
这里只是我的demo里写的Adapter,也可以写成带更多内容的Adapter。addContent里带的参数view就是getView里面返回的view,这样就可以根据inflate的布局设置内容了,getView返回的布局page_layout.xml如下:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/cover" >
-
- <TextView
- android:id="@+id/content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="60dp"
- android:padding="10dp"
- android:textColor="#000000"
- android:textSize="22sp" />
-
- <TextView
- android:id="@+id/index"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true"
- android:layout_marginBottom="60dp"
- android:textColor="#000000"
- android:textSize="30sp" />
-
- </RelativeLayout>
只包含了两个TextView,所以在adapter中可以根据id查找到这两个TextView再给它设置内容。 OK了,MainActivity的布局如下:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <com.jingchen.pagerdemo.ScanView
- android:id="@+id/scanview"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </RelativeLayout>
很简单,只包含了ScanView。 MainActivity的代码:
- package com.jingchen.pagerdemo;
-
- import java.util.ArrayList;
- import java.util.List;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.Menu;
-
- public class MainActivity extends Activity
- {
- ScanView scanview;
- ScanViewAdapter adapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- scanview = (ScanView) findViewById(R.id.scanview);
- List<String> items = new ArrayList<String>();
- for (int i = 0; i < 8; i++)
- items.add("第 " + (i + 1) + " 页");
- adapter = new ScanViewAdapter(this, items);
- scanview.setAdapter(adapter);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu)
- {
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
-
- }
给ScanView设置Adapter就可以了。 好啦,仿多看的平移翻页就完成了~