scrollview的一个分页效果,即上拉到一定程度,就自动跳转到另一个布局。就像京东淘宝的商品详情。这是一个改良版,可以由下部分到顶部。

写在前面的

源码地址

参考其他大神的作品,搬过来自己按照自己想要的效果改了改,作为新手这些问题是在恼火。

在运用ScrollViewContainer的时候,感觉真心不错,但是有一个问题,就是我在下面的scrollview里面,怎么到顶部呢?

查了不少资料,没有得到答案,相关的额全部是在求这一类的解决办法,后来我想了想,另辟捷径,做了一个比较水的效果,

希望大家看了,有改善的的意见或建议都提出来,用意完善这一个控件。

  • 思路:
    在内部无法实现这样的方法,那么可以用外部刺激来完成吗?
  • 试验1:
    ScrollViewContainer针对父容器到顶部。 结果:失败,原因(当ScrollViewContainer被分页之后,虽然是容器,但是焦点都在当前的scrollview,所以无法实现)
  • 试验2:
    隐藏底部的scrollview。。。。。(当然很蠢的办法,大家无视掉)
  • 试验3:
    这个是突然地灵光一闪,手指滑动可以翻页,内部的构造方法不行,那么我能不能模拟手势滑动呢?

这么一想,可怕,于是就这么天真的行了。代码如下:

MainActivity


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
	private TextView top_text;
	private ScrollViewContainer all_scrollView;// 核心部分,为自定义的scrollview容器
	private ScrollView scr1, scr2;// 两个scrollview
	boolean gh = true;// 点击标识。防止多次点击
	boolean ScrollViewConcanPullUp, ScrollViewConcanPullDown;// 翻页标识(后面会解释其用意)
	public static TextView title;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		title = (TextView) findViewById(R.id.title);
		top_text = (TextView) findViewById(R.id.top_text);
		all_scrollView = (ScrollViewContainer) findViewById(R.id.all_scrollView);
		scr1 = (ScrollView) findViewById(R.id.scr1);
		scr2 = (ScrollView) findViewById(R.id.scr2);
		// 到顶部的View点击事件
		top_text.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				if (!gh) {
					return;
				}
				gh = false;
				ScrollViewConcanPullUp = ScrollViewContainer.canPullUp;// 得到控件的翻页标识
				ScrollViewConcanPullDown = ScrollViewContainer.canPullDown;
				ScrollViewContainer.canPullDown = true;// 强制设置为true(true为可翻页,false反之)
				ScrollViewContainer.canPullUp = true;
				handler2.sendEmptyMessage(22);// 异步触发已检测事件(检测似乎翻页成功)
				scr2.scrollTo(0, 0);// 在下面的scrollview里面,先执行到底部的scrollview顶部
				SystemClock.sleep(50);// 等待时间(快速到底部,有一个惯性效果,如果马上触发scrollview,会有一个阻尼效果,导致不能翻页成功,所以,最后采取等一段时间scrollview自动恢复)
				RunStart(0);// 翻页步骤(翻页完成的之后,顶部的scrollview再到顶部,整个流程就算完成了)
			}
		});
	}

	/**
	 * 滑动手势模拟(到顶部)
	 * 
	 * @param u
	 */
	private void RunStart(final int u) {
		SystemClock.sleep(50);
		new Thread() {
			@Override
			public void run() {
				final long downTime = SystemClock.currentThreadTimeMillis();
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						MotionEvent down = MotionEvent.obtain(downTime, SystemClock.currentThreadTimeMillis(), MotionEvent.ACTION_DOWN, 100, 80, 0);// 点击模拟
						all_scrollView.dispatchTouchEvent(down);
					}
				});
				SystemClock.sleep(50);
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						MotionEvent move = MotionEvent.obtain(downTime, SystemClock.currentThreadTimeMillis(), MotionEvent.ACTION_MOVE, 100, 400, 0);// 滑动模拟
						all_scrollView.dispatchTouchEvent(move);
					}
				});
				SystemClock.sleep(150);
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						MotionEvent up = MotionEvent.obtain(downTime, SystemClock.currentThreadTimeMillis(), MotionEvent.ACTION_UP, 100, 400, 0);// 手指抬起(模拟事件完成)
						all_scrollView.dispatchTouchEvent(up);
					}
				});
			}
		}.start();
		if (u == 2) {// 滑动事件执行完成。不论是否翻页成功,都把缓存起来的标识给还回去
			ScrollViewContainer.canPullUp = ScrollViewConcanPullUp;
			ScrollViewContainer.canPullDown = ScrollViewConcanPullDown;
			scr1.scrollTo(0, 0);// 上部份scrollview到顶部(需要动画流畅的,建议这一步放到检测是否成功的方法里面去,即放在翻页成功里面去,效果看起来会更连贯)
		}
		if (u == 0) {
			SystemClock.sleep(50);
			RunStart(2);
		}
	}

	Handler handler2 = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 22) {
				handler2.postAtTime(new Runnable() {
					@Override
					public void run() {
						System.out.println("ScrollViewContainer.mCurrentViewIndex__>" + ScrollViewContainer.mCurrentViewIndex);
						if (ScrollViewContainer.mCurrentViewIndex == 0) {// mCurrentViewIndex+> 0代表上部分的scrollview 1 代表下半部分的scrollview
							System.out.println("翻页完成");
						} else {
							System.out.println("翻页失败");// 翻页失败 (这里可以做一些失败的操作,比如提示一下,翻页失败什么的)
							Toast.makeText(MainActivity.this, "翻页失败", 0).show();
							System.out.println("ScrollViewContainer__>" + ScrollViewContainer.mCurrentViewIndex);
						}
						gh = true; 点击标识。防止多次点击,等到此次事件完成,才能触发下一次的翻页
					}
				}, SystemClock.uptimeMillis() + 650);
			}
		};
	};
}

ScrollViewContainer


import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

/**
 * 包含两个ScrollView的容器
 */
public class ScrollViewContainer extends RelativeLayout {

	/**
	 * 自动上滑
	 */
	public static final int AUTO_UP = 0;
	/**
	 * 自动下滑
	 */
	public static final int AUTO_DOWN = 1;
	/**
	 * 动画完成
	 */
	public static final int DONE = 2;
	/**
	 * 动画速度
	 */
	public static final float SPEED = 6.5f;

	private boolean isMeasured = false;

	/**
	 * 用于计算手滑动的速度
	 */
	private static VelocityTracker vt;

	private int mViewHeight;
	private int mViewWidth;

	private View topView;
	private View bottomView;

	public static boolean canPullDown;
	public static boolean canPullUp;
	private static int state = DONE;

	/**
	 * 记录当前展示的是哪个view,0是topView,1是bottomView
	 */
	public static int mCurrentViewIndex = 0;
	/**
	 * 手滑动距离,这个是控制布局的主要变量
	 */
	private float mMoveLen;
	private static MyTimer mTimer;
	private float mLastY;
	/**
	 * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以拖拽了,mEvents==-1时可以舍弃将要到来的第一个move事件,
	 * 这点是去除多点拖动剧变的关键
	 */
	private int mEvents;

	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			if (mMoveLen != 0) {
				if (state == AUTO_UP) {
					mMoveLen -= SPEED;
					if (mMoveLen <= -mViewHeight) {
						mMoveLen = -mViewHeight;
						state = DONE;
						mCurrentViewIndex = 1;
						System.out.println("1___________+_>" + mCurrentViewIndex);
						MainActivity.title.setText("你在第二页   可以试一试到顶部");
					}
				} else if (state == AUTO_DOWN) {
					mMoveLen += SPEED;
					if (mMoveLen >= 0) {
						mMoveLen = 0;
						state = DONE;
						mCurrentViewIndex = 0;
						System.out.println("2_______________+_>" + mCurrentViewIndex);
						MainActivity.title.setText("你在第一页   向下滑动到第二页");
					}
				} else {
					mTimer.cancel();
				}
			}
			requestLayout();
		}
	};

	public ScrollViewContainer(Context context) {
		super(context);
		init();
	}

	public ScrollViewContainer(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	private void init() {
		mTimer = new MyTimer(handler);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		switch (ev.getActionMasked()) {
		case MotionEvent.ACTION_DOWN:
			if (vt == null)
				vt = VelocityTracker.obtain();
			else
				vt.clear();
			mLastY = ev.getY();
			vt.addMovement(ev);
			mEvents = 0;
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
		case MotionEvent.ACTION_POINTER_UP:
			// 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug
			mEvents = -1;
			break;
		case MotionEvent.ACTION_MOVE:
			vt.addMovement(ev);
			if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {
				mMoveLen += (ev.getY() - mLastY);
				// 防止上下越界
				if (mMoveLen > 0) {
					mMoveLen = 0;
					mCurrentViewIndex = 0;
				} else if (mMoveLen < -mViewHeight) {
					mMoveLen = -mViewHeight;
					mCurrentViewIndex = 1;
				}
				if (mMoveLen < -8) {
					// 防止事件冲突
					ev.setAction(MotionEvent.ACTION_CANCEL);
				}
			} else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {
				mMoveLen += (ev.getY() - mLastY);
				// 防止上下越界
				if (mMoveLen < -mViewHeight) {
					mMoveLen = -mViewHeight;
					mCurrentViewIndex = 1;
				} else if (mMoveLen > 0) {
					mMoveLen = 0;
					mCurrentViewIndex = 0;
				}
				if (mMoveLen > 8 - mViewHeight) {
					// 防止事件冲突
					ev.setAction(MotionEvent.ACTION_CANCEL);
				}
			} else
				mEvents++;
			mLastY = ev.getY();
			requestLayout();
			break;
		case MotionEvent.ACTION_UP:
			mLastY = ev.getY();
			vt.addMovement(ev);
			vt.computeCurrentVelocity(700);
			// 获取Y方向的速度
			float mYV = vt.getYVelocity();
			if (mMoveLen == 0 || mMoveLen == -mViewHeight)
				break;
			if (Math.abs(mYV) < 500) {
				// 速度小于一定值的时候当作静止释放,这时候两个View往哪移动取决于滑动的距离
				if (mMoveLen <= -mViewHeight / 2) {
					state = AUTO_UP;
				} else if (mMoveLen > -mViewHeight / 2) {
					state = AUTO_DOWN;
				}
			} else {
				// 抬起手指时速度方向决定两个View往哪移动
				if (mYV < 0)
					state = AUTO_UP;
				else
					state = AUTO_DOWN;
			}
			mTimer.schedule(2);
			// try {
			// vt.recycle();
			// } catch (IllegalStateException e) {
			// e.printStackTrace();
			// }
			break;
		}
		try {
			super.dispatchTouchEvent(ev);
		} catch (Exception e) {
		}
		return true;
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		topView.layout(0, (int) mMoveLen, mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen);
		bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen, mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen + bottomView.getMeasuredHeight());
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		if (!isMeasured) {
			isMeasured = true;

			mViewHeight = getMeasuredHeight();// - dp2px(60)
			mViewWidth = getMeasuredWidth();

			topView = getChildAt(0);
			bottomView = getChildAt(1);

			bottomView.setOnTouchListener(bottomViewTouchListener);
			topView.setOnTouchListener(topViewTouchListener);
		}
	}

	private OnTouchListener topViewTouchListener = new OnTouchListener() {

		@Override
		public boolean onTouch(View v, MotionEvent event) {
			ScrollView sv = (ScrollView) v;
			if (sv.getScrollY() == (sv.getChildAt(0).getMeasuredHeight() - sv.getMeasuredHeight()) && mCurrentViewIndex == 0)
				canPullUp = true;
			else
				canPullUp = false;
			return false;
		}
	};
	private OnTouchListener bottomViewTouchListener = new OnTouchListener() {

		@Override
		public boolean onTouch(View v, MotionEvent event) {
			ScrollView sv = (ScrollView) v;
			if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)
				canPullDown = true;
			else
				canPullDown = false;
			return false;
		}
	};

	class MyTimer {
		private Handler handler;
		private Timer timer;
		private MyTask mTask;

		public MyTimer(Handler handler) {
			this.handler = handler;
			timer = new Timer();
		}

		public void schedule(long period) {
			if (mTask != null) {
				mTask.cancel();
				mTask = null;
			}
			mTask = new MyTask(handler);
			timer.schedule(mTask, 0, period);
		}

		public void cancel() {
			if (mTask != null) {
				mTask.cancel();
				mTask = null;
			}
		}

		class MyTask extends TimerTask {
			private Handler handler;

			public MyTask(Handler handler) {
				this.handler = handler;
			}

			@Override
			public void run() {
				handler.obtainMessage().sendToTarget();
			}

		}
	}

	// 将dp转化为px
	private int dp2px(int dp) {
		return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
	}
}

AndroidManifest.xml

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Super ScrollView for UGUI提供基于UGUI ScrollRect的可轻松定制的ScrollView。它是一组C#脚本,可帮助您创建所需的ScrollView这是非常强大的和高度优化的性能。 文件 Android演示应用程序 演示: - 聊天消息列表演示 - 水平画廊演示 - 垂直画廊演示 - GridView演示 - PageVew演示 - TreeVew演示 - 与稠粘头演示的TreeView - 旋转日期选择器 - 更改项目高度演示 - 下拉刷新演示 - 拉起来加载更多的演示 - 点击加载更多演示 - 选择并删除演示 - GridView删除项目演示 - 顶部到底部的演示 - 自下而上的演示 - 从左到右的演示 - 右侧演示 - 响应GridView演示 - TreeViewWithChildrenIndent演示 特征: - ListView和GridView和TreeView - 无限的项目 - 项目在不同的大小(高度/宽度) - 具有不同预制的物品 - 在初始时间大小未知的项目 - 垂直滚动视图(从上到下,从下到上) - 水平滚动视图(从左到右,从右到左) - 项目填充 - 滚动到指定的项目 - 滚动到具有偏移量的项目 - 项目计数在运行时更改 - 项目大小(高度/宽度)在运行时更改 - 物品捕捉到视口中的任何位置 - 项目循环,如微调 - 添加/删除项目 - 全部删除/删除所有项目 - 刷新并重新加载项目 - 使用池缓存项目,不要在运行时销毁项目 - 有效回收物品 - 平台无关 - UGUI支持 - 支持Unity平台(IOS / Android / Mac / PC / Console / Winphone / WebGL ...)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值