Android实现多页左右滑动效果,支持子view动态创建和cache
要实现多页滑动效果,主要是需要处理onTouchEvent和onInterceptTouchEvent,要处理好touch事件的子控件和父控件的传递问题。
滚动控制可以利用android的Scroller来实现。
这里提供两种做法:
1、自定义MFlipper控件,从ViewGroup继承,利用Scroller实现滚动,重点是onTouchEvent和onInterceptTouchEvent的重写,
要注意什么时候该返回true,什么时候false。否则会导致界面滑动和界面内按钮点击事件相冲突。
由于采用了ViewGroup来管理子view,只适合于页面数较少而且较固定的情况,因为viewgroup需要一开始就调用addView,把所有view都加进去并layout,
太多页面会有内存问题。如果是页面很多,而且随时动态增长的话,就需要考虑对view做cache和动态创建,动态layout,具体做法参考下面的方法二;
2、从AdapterView继承,参考Android自带ListView的实现,实现子view动态创建和cache,滑动效果等。
源码如下:
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.util.SparseArray;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.View;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup;
- import android.widget.AdapterView;
- import android.widget.BaseAdapter;
- import android.widget.Gallery;
- import android.widget.Scroller;
- /**
- * 自定义一个横向滚动的AdapterView,类似与全屏的Gallery,但是一次只滚动一屏,而且每一屏支持子view的点击处理
- * @author weibinke
- *
- */
- public class MultiPageSwitcher extends AdapterView<BaseAdapter> {
- private BaseAdapter mAdapter = null;
- private Scroller mScroller;
- private int mTouchSlop;
- private float mTouchStartX;
- private float mLastMotionX;
- private final static String TAG = "MultiPageSwitcher";
- private int mLastScrolledOffset = 0;
- /** User is not touching the list */
- private static final int TOUCH_STATE_RESTING = 0;
- /** User is scrolling the list */
- private static final int TOUCH_STATE_SCROLL = 2;
- private int mTouchState = TOUCH_STATE_RESTING;
- private int mHeightMeasureSpec;
- private int mWidthMeasureSpec;
- private int mSelectedPosition;
- private int mFirstPosition; //第一个可见view的position
- private int mCurrentSelectedPosition;
- private VelocityTracker mVelocityTracker;
- private static final int SNAP_VELOCITY = 600;
- protected RecycleBin mRecycler = new RecycleBin();
- private OnPostionChangeListener mOnPostionChangeListener = null;
- public MultiPageSwitcher(Context context, AttributeSet attrs) {
- super(context, attrs);
- mScroller = new Scroller(context);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right,
- int bottom) {
- // TODO Auto-generated method stub
- MLog.d("MultiPageSwitcher.onlayout start");
- super.onLayout(changed, left, top, right, bottom);
- if (mAdapter == null) {
- return ;
- }
- recycleAllViews();
- detachAllViewsFromParent();
- mRecycler.clear();
- fillAllViews();
- MLog.d("MultiPageSwitcher.onlayout end");
- }
- /**
- * 从当前可见的view向左边填充
- */
- private void fillToGalleryLeft() {
- int itemSpacing = 0;
- int galleryLeft = 0;
- // Set state for initial iteration
- View prevIterationView = getChildAt(0);
- int curPosition;
- int curRightEdge;
- if (prevIterationView != null) {
- curPosition = mFirstPosition - 1;
- curRightEdge = prevIterationView.getLeft() - itemSpacing;
- } else {
- // No children available!
- curPosition = 0;
- curRightEdge = getRight() - getLeft();
- }
- while (curRightEdge > galleryLeft && curPosition >= 0) {
- prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
- curRightEdge, false);
- // Remember some state
- mFirstPosition = curPosition;
- // Set state for next iteration
- curRightEdge = prevIterationView.getLeft() - itemSpacing;
- curPosition--;
- }
- }
- private void fillToGalleryRight() {
- int itemSpacing = 0;
- int galleryRight = getRight() - getLeft();
- int numChildren = getChildCount();
- int numItems = mAdapter.getCount();
- // Set state for initial iteration
- View prevIterationView = getChildAt(numChildren - 1);
- int curPosition;
- int curLeftEdge;
- if (prevIterationView != null) {
- curPosition = mFirstPosition + numChildren;
- curLeftEdge = prevIterationView.getRight() + itemSpacing;
- } else {
- mFirstPosition = curPosition = numItems - 1;
- curLeftEdge = 0;
- }
- while (curLeftEdge < galleryRight && curPosition < numItems) {
- prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
- curLeftEdge, true);
- // Set state for next iteration
- curLeftEdge = prevIterationView.getRight() + itemSpacing;
- curPosition++;
- }
- }
- /**
- *填充view
- */
- private void fillAllViews(){
- //先创建第一个view,使其居中显示
- if (mSelectedPosition >= mAdapter.getCount()&& mSelectedPosition > 0) {
- //处理被记录被删除导致当前选中位置超出记录数的情况
- mSelectedPosition = mAdapter.getCount() - 1;
- if(mOnPostionChangeListener != null){
- mCurrentSelectedPosition = mSelectedPosition;
- mOnPostionChangeListener.onPostionChange(this, mCurrentSelectedPosition);
- }
- }
- mFirstPosition = mSelectedPosition;
- mCurrentSelectedPosition = mSelectedPosition;
- View child = makeAndAddView(mSelectedPosition, 0, 0, true);
- int offset = getWidth() / 2 - (child.getLeft() + child.getWidth() / 2);
- child.offsetLeftAndRight(offset);
- fillToGalleryLeft();
- fillToGalleryRight();
- }
- /**
- * Obtain a view, either by pulling an existing view from the recycler or by
- * getting a new one from the adapter. If we are animating, make sure there
- * is enough information in the view's layout parameters to animate from the
- * old to new positions.
- *
- * @param position Position in the gallery for the view to obtain
- * @param offset Offset from the selected position
- * @param x X-coordintate indicating where this view should be placed. This
- * will either be the left or right edge of the view, depending on
- * the fromLeft paramter
- * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
- * building from left to right)?
- * @return A view that has been added to the gallery
- */
- private View makeAndAddView(int position, int offset, int x,
- boolean fromLeft) {
- View child;
- //ask the adapter for a view
- child = mAdapter.getView(position, null, this);
- // Position the view
- setUpChild(child, offset, x, fromLeft);
- return child;
- }
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- /*
- * Gallery expects Gallery.LayoutParams.
- */
- return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- /**
- * Helper for makeAndAddView to set the position of a view and fill out its
- * layout paramters.
- *
- * @param child The view to position
- * @param offset Offset from the selected position
- * @param x X-coordintate indicating where this view should be placed. This
- * will either be the left or right edge of the view, depending on
- * the fromLeft paramter
- * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
- * building from left to right)?
- */
- private void setUpChild(View child, int offset, int x, boolean fromLeft) {
- // Respect layout params that are already in the view. Otherwise
- // make some up...
- Gallery.LayoutParams lp = (Gallery.LayoutParams)
- child.getLayoutParams();
- if (lp == null) {
- lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
- }
- addViewInLayout(child, fromLeft ? -1 : 0, lp);
- child.setSelected(offset == 0);
- // Get measure specs
- int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
- 0, lp.height);
- int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
- 0, lp.width);
- // Measure child
- child.measure(childWidthSpec, childHeightSpec);
- int childLeft;
- int childRight;
- // Position vertically based on gravity setting
- int childTop = 0;
- int childBottom = childTop + child.getMeasuredHeight();
- int width = child.getMeasuredWidth();
- if (fromLeft) {
- childLeft = x;
- childRight = childLeft + width;
- } else {
- childLeft = x - width;
- childRight = x;
- }
- child.layout(childLeft, childTop, childRight, childBottom);
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // TODO Auto-generated method stub
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mWidthMeasureSpec = widthMeasureSpec;
- mHeightMeasureSpec = heightMeasureSpec;
- }
- @Override
- public int getCount() {
- // TODO Auto-generated method stub
- return mAdapter.getCount();
- }
- @Override
- public BaseAdapter getAdapter() {
- // TODO Auto-generated method stub
- return mAdapter;
- }
- @Override
- public void setAdapter(BaseAdapter adapter) {
- // TODO Auto-generated method stub
- mAdapter = adapter;
- removeAllViewsInLayout();
- requestLayout();
- }
- @Override
- public View getSelectedView() {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public void setSelection(int position) {
- // TODO Auto-generated method stub
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (!mScroller.isFinished()) {
- return true;
- }
- final int action = event.getAction();
- MLog.d("onInterceptTouchEvent action = "+event.getAction());
- if (MotionEvent.ACTION_DOWN == action) {
- startTouch(event);
- return false;
- }else if (MotionEvent.ACTION_MOVE == action) {
- return startScrollIfNeeded(event);
- }else if (MotionEvent.ACTION_UP == action || MotionEvent.ACTION_CANCEL == action) {
- mTouchState = TOUCH_STATE_RESTING;
- return false;
- }
- return false;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (!mScroller.isFinished()) {
- return true;
- }
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- MLog.d("onTouchEvent action = "+event.getAction());
- final int action = event.getAction();
- final float x = event.getX();
- if (MotionEvent.ACTION_DOWN == action) {
- startTouch(event);
- }else if (MotionEvent.ACTION_MOVE == action) {
- if (mTouchState == TOUCH_STATE_RESTING) {
- startScrollIfNeeded(event);
- }else if (mTouchState == TOUCH_STATE_SCROLL) {
- int deltaX = (int)(x - mLastMotionX);
- mLastMotionX = x;
- scrollDeltaX(deltaX);
- }
- }else if (MotionEvent.ACTION_UP == action || MotionEvent.ACTION_CANCEL == action) {
- if (mTouchState == TOUCH_STATE_SCROLL) {
- onUp(event);
- }
- }
- return true;
- }
- private void scrollDeltaX(int deltaX){
- //先把现有的view坐标移动
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).offsetLeftAndRight(deltaX);
- }
- boolean toLeft = (deltaX < 0);
- detachOffScreenChildren(toLeft);
- if (deltaX < 0) {
- //sroll to right
- fillToGalleryRight();
- }else {
- fillToGalleryLeft();
- }
- invalidate();
- int position = calculteCenterItem() + mFirstPosition;
- if (mCurrentSelectedPosition != position) {
- mCurrentSelectedPosition = position;
- if (mOnPostionChangeListener != null) {
- mOnPostionChangeListener.onPostionChange(this, mCurrentSelectedPosition);
- }
- }
- }
- private void onUp(MotionEvent event){
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000);
- int velocityX = (int) velocityTracker.getXVelocity();
- MLog.d( "onUp velocityX:"+velocityX);
- if (velocityX < -SNAP_VELOCITY && mSelectedPosition < mAdapter.getCount() - 1) {
- if (scrollToChild(mSelectedPosition + 1)) {
- mSelectedPosition ++;
- }
- }else if (velocityX > SNAP_VELOCITY && mSelectedPosition > 0) {
- if (scrollToChild(mSelectedPosition - 1)) {
- mSelectedPosition --;
- }
- }else{
- int position = calculteCenterItem();
- int newpostion = mFirstPosition + position;
- if (scrollToChild(newpostion)) {
- mSelectedPosition = newpostion;
- }
- }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mTouchState = TOUCH_STATE_RESTING;
- }
- /**
- * 计算最接近中心点的view
- * @return
- */
- private int calculteCenterItem(){
- View child = null;
- int lastpostion = 0;
- int lastclosestDistance = 0;
- int viewCenter = getLeft() + getWidth() / 2;
- for (int i = 0; i < getChildCount(); i++) {
- child = getChildAt(i);
- if (child.getLeft() < viewCenter && child.getRight() > viewCenter ) {
- lastpostion = i;
- break;
- }else {
- int childClosestDistance = Math.min(Math.abs(child.getLeft() - viewCenter), Math.abs(child.getRight() - viewCenter));
- if (childClosestDistance < lastclosestDistance) {
- lastclosestDistance = childClosestDistance;
- lastpostion = i;
- }
- }
- }
- return lastpostion;
- }
- public void moveNext(){
- if (!mScroller.isFinished()) {
- return;
- }
- if (0 <= mSelectedPosition && mSelectedPosition < mAdapter.getCount() - 1) {
- if (scrollToChild(mSelectedPosition + 1)) {
- mSelectedPosition ++;
- }else {
- makeAndAddView(mSelectedPosition + 1, 1, getWidth(), true);
- if (scrollToChild(mSelectedPosition + 1)) {
- mSelectedPosition ++;
- }
- }
- }
- }
- public void movePrevious(){
- if (!mScroller.isFinished()) {
- return;
- }
- if (0 < mSelectedPosition && mSelectedPosition < mAdapter.getCount()) {
- if (scrollToChild(mSelectedPosition -1)) {
- mSelectedPosition --;
- }else {
- makeAndAddView(mSelectedPosition - 1, -1, 0, false);
- mFirstPosition = mSelectedPosition - 1;
- if (scrollToChild(mSelectedPosition - 1)) {
- mSelectedPosition --;
- }
- }
- }
- }
- private boolean scrollToChild(int position){
- MLog.d( "scrollToChild positionm,FirstPosition,childcount:"+position + "," + mFirstPosition+ "," + getChildCount());
- View child = getChildAt(position - mFirstPosition );
- if (child != null) {
- int distance = getWidth() / 2 - (child.getLeft() + child.getWidth() / 2);
- mLastScrolledOffset = 0;
- mScroller.startScroll(0, 0, distance, 0,200);
- invalidate();
- return true;
- }
- MLog.d( "scrollToChild some error happened");
- return false;
- }
- @Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- int scrollX = mScroller.getCurrX();
- scrollDeltaX(scrollX - mLastScrolledOffset);
- mLastScrolledOffset = scrollX;
- postInvalidate();
- }
- }
- private void startTouch(MotionEvent event){
- mTouchStartX = event.getX();
- mTouchState = mScroller.isFinished()? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLL;
- mLastMotionX = mTouchStartX;
- }
- private boolean startScrollIfNeeded(MotionEvent event){
- final int xPos = (int)event.getX();
- mLastMotionX = event.getX();
- if (xPos < mTouchStartX - mTouchSlop
- || xPos > mTouchStartX + mTouchSlop
- ) {
- // we've moved far enough for this to be a scroll
- mTouchState = TOUCH_STATE_SCROLL;
- return true;
- }
- return false;
- }
- /**
- * Detaches children that are off the screen (i.e.: Gallery bounds).
- *
- * @param toLeft Whether to detach children to the left of the Gallery, or
- * to the right.
- */
- private void detachOffScreenChildren(boolean toLeft) {
- int numChildren = getChildCount();
- int start = 0;
- int count = 0;
- int firstPosition = mFirstPosition;
- if (toLeft) {
- final int galleryLeft = 0;
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child.getRight() >= galleryLeft) {
- break;
- } else {
- count++;
- mRecycler.put(firstPosition + i, child);
- }
- }
- } else {
- final int galleryRight = getWidth();
- for (int i = numChildren - 1; i >= 0; i--) {
- final View child = getChildAt(i);
- if (child.getLeft() <= galleryRight) {
- break;
- } else {
- start = i;
- count++;
- mRecycler.put(firstPosition + i, child);
- }
- }
- }
- detachViewsFromParent(start, count);
- if (toLeft) {
- mFirstPosition += count;
- }
- mRecycler.clear();
- }
- public void setOnPositionChangeListen(OnPostionChangeListener onPostionChangeListener){
- mOnPostionChangeListener = onPostionChangeListener;
- }
- public int getCurrentSelectedPosition(){
- return mCurrentSelectedPosition;
- }
- /**
- * 刷新数据,本来想用AdapterView.AdapterDataSetObserver机制来实现的,但是整个逻辑移植比较麻烦,就暂时用这个替代了
- */
- public void updateData(){
- requestLayout();
- }
- private void recycleAllViews() {
- int childCount = getChildCount();
- final RecycleBin recycleBin = mRecycler;
- // All views go in recycler
- for (int i=0; i<childCount; i++) {
- View v = getChildAt(i);
- int index = mFirstPosition + i;
- recycleBin.put(index, v);
- }
- }
- class RecycleBin {
- private SparseArray<View> mScrapHeap = new SparseArray<View>();
- public void put(int position, View v) {
- if (mScrapHeap.get(position) != null) {
- Log.e(TAG,"RecycleBin put error.");
- }
- mScrapHeap.put(position, v);
- }
- View get(int position) {
- // System.out.print("Looking for " + position);
- View result = mScrapHeap.get(position);
- if (result != null) {
- MLog.d("RecycleBin get hit.");
- mScrapHeap.delete(position);
- } else {
- MLog.d("RecycleBin get Miss.");
- }
- return result;
- }
- View peek(int position) {
- // System.out.print("Looking for " + position);
- return mScrapHeap.get(position);
- }
- void clear() {
- final SparseArray<View> scrapHeap = mScrapHeap;
- final int count = scrapHeap.size();
- for (int i = 0; i < count; i++) {
- final View view = scrapHeap.valueAt(i);
- if (view != null) {
- removeDetachedView(view, true);
- }
- }
- scrapHeap.clear();
- }
- }
- public interface OnPostionChangeListener{
- abstract public void onPostionChange(View v,int position);
- }
- }
Android实现多页左右滑动效果,支持子view动态创建和cache的更多相关文章
- Android实现浮层的上下滑动(支持内部加入View)
前言 我K.今天竟然是情人节.对于资深的单身狗来说,简直是个噩耗,今天注定是各种秀恩爱.心塞中.. .. 话题到此结束,管他什么情人节,今天给大家带来的是一个浮层的上下滑动,浮层滑动时分三种状态:所有 ...
- 011 Android TabLayout+ViewPager实现顶部滑动效果(多个页面)
1.TabLayout介绍 TabLayout提供了一个水平的布局用来展示Tabs,很多应用都有这样的设计,典型的有网易新闻,简书,知乎等.TabLayout就可以很好的完成这一职责,首先TabLay ...
- android SlidingTabLayout实现ViewPager页卡滑动效果
先来张效果图(能够滑动切换页卡) watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcGVuZ2t2/font/5a6L5L2T/fontsize/400/fi ...
- Android使用GestureDetector实现手势滑动效果
直接看实例: package com.example.gesturedetector; import android.os.Bundle; import android.app.Activity; i ...
- Android学习之-TextView的滑动效果
textView中如何设置滚动条 在xml中定义: <TextView android:layout_width="wrap_content" ...
- Android实现左右滑动效果
本示例演示在Android中实现图片左右滑动效果. 关于滑动效果,在Android中用得比较多,本示例实现的滑动效果是使用ViewFlipper来实现的,当然也可以使用其它的View来实现.接下来 ...
- Android 实现左右滑动效果ViewFlipper终结【转】
本示例演示在Android中实现图片左右滑动效果. 关于滑动效果,在Android中用得比较多,本示例实现的滑动效果是使用ViewFlipper来实现的,当然也可以使用其它的View来实现.接下来 ...
- Android -- 常见控件的小效果
1,EditText控件 ① 修改光标颜色 自定义drawable 创建cursor.xml文件 <?xml version="1.0" encoding="utf ...
- Android使用ViewFlipper实现左右滑动效果面
在我的博客中,上次是使用ViewPager实现左右滑动的效果的,请看文章:Android使用ViewPager实现左右滑动效果. 这次我来使用ViewFlipper实现这种效果,好了,先看看效果吧: ...
随机推荐
- phpcms的增删改查操作整理
一.查 ①select($where = '', $data = '*', $limit = '', $order = '', $group = '', $key='') /** * 执行sql查询 ...
- 50+ 响应式的Prestashop电商主题
PrestaShop是一款针对web2.0设计的全功能.跨平台的免费开源电子商务解决方案,自08年1.0版本发布,短短两年时间,发展迅速,全球已超过四万家网店采用Prestashop进行部署.Pres ...
- 14个超赞的响应式HTML5模板免费下载
现在HTML5已经势不可挡.很多朋友开始学习HTML5,当你已经学习过一些HTML5的教程之后,是不是想立即开始实战了呢?对,现在就开始吧,不过 找一些优秀的HTML5模板案例练习是相当不错的注意.当 ...
- navicat 或者workbench 无法连接127.0.0.1(61)的解决方法
1.输入mysql -uroot 进入命令行模式, 2.输入"show variables like '%sock%';"查看sock文件所在位置 如: 3.配置客户端(以navi ...
- 未能加载文件或程序集“Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。
未能加载文件或程序集“Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个 ...
- 转】Maven学习总结(九)——使用Nexus搭建Maven私服
原博文出自于:http://www.cnblogs.com/xdp-gacl/p/4068967.html 感谢! 一.搭建nexus私服的目的 为什么要搭建nexus私服,原因很简单,有些公司都不提 ...
- How Tomcat Works(十四)
我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine.Host.Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明. 对于每个 ...
- 使用struts2实现文件上传
action中 private File file;//文件 private String fileFileName;//文件名字 固定格式name+FileName private String f ...
- Eclipse 和 NetBeans 快捷键即其他常用功能比较
按: 自己用 Eclipse, 常用的也就这些功能, 在用 NetBeans 时, 有些不顺手, 因此列表如下. Eclipse和NetBeans常用快捷键对比: 功能 Eclipse N ...
- Mac生存手册
最近刚从Linux转到了Mac系统上,感觉好的地方是再也不折腾了,什么GNOME, KDE, XFCE,各种发行版本都远离我而去了.当然Mac下很多好软件都是要付费的,我只能绕着走了: 1. 命令行, ...