当然,这里说的gallery不是系统的gallery,而是自定义的一个控件,实现和gallery一样的滑动效果。
一开始我试过使用系统自带的gallery控件来实现可以左右滑动并且显示的是webview控件里面的内容,但是这种方法没有成功,具体情况是webview可以响应手势事件,可以上下滑动等,但是外层的gallery根本拿不到手势事件。其原因是webview首先获得手势事件,并且全部吞掉了用于处理webview中的网页缩放或者滑动查看,外层的activity和gallery都得不到这个事件。如果把webview的事件屏蔽掉(自定义个一个webview,并且在ontouchevent中拦截,返回false),这样的话gallery可以正常滑动,可是webview中的内容就没有办法滚动来查看。
出现这种问题,我的另一个思路就是自定义一个控件来实现这样的效果,网上有使用viewflipper实现的,不过那种效果是程序一下就切换过去,不是手势慢慢控制移动。这里借鉴了另一篇博文,他介绍了如何自定义一个viewgroup,并且使用scroller来实现触摸滑屏的效果。
首先上效果图:
最重要的一个自定义viewgroup控件
public class WebViewGallery extends ViewGroup {
private final static String TAG = WebViewGallery.class.getSimpleName();
private Context mContext;
private int curScreen = 0 ; //当前屏
private Scroller mScroller = null ;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private int mTouchState = TOUCH_STATE_REST;
//处理触摸事件 ~
public static int SNAP_VELOCITY = 600 ; //大于
private int mTouchSlop = 100 ; //初始化一个最小滑动距离,手指滑动距离需要大于此值系统才会开始移动控件
private float mLastionMotionX = 0 ;
private VelocityTracker mVelocityTracker = null ; //处理触摸的速率
//----------------
private OnWebviewSwitchListener mOnWebviewSwitchListener;
public WebViewGallery(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
private void init() {
mScroller = new Scroller(mContext);
}
public void addWebViews(List<WebView> webviewlist){
if (webviewlist != null && webviewlist.size() != 0){
Iterator<WebView> iterator = webviewlist.iterator();
while (iterator.hasNext()){
addView(iterator.next());
}
invalidate();
}
}
public void setOnWebviewSwitchListener(OnWebviewSwitchListener onWebviewSwitchListener){
mOnWebviewSwitchListener = onWebviewSwitchListener;
}
// 只有当前LAYOUT中的某个CHILD导致SCROLL发生滚动,才会致使自己的COMPUTESCROLL被调用
@Override
public void computeScroll() {
// 如果返回true,表示动画还没有结束
// 因为前面startScroll,所以只有在startScroll完成时 才会为false
if (mScroller.computeScrollOffset()) {
// 产生了动画效果 每次滚动一点
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 刷新View 否则效果可能有误差
postInvalidate();
}
}
// 这个感觉没什么作用 不管true还是false 都是会执行onTouchEvent的 因为子view里面onTouchEvent返回false了
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
//表示已经开始滑动了,不需要走该Action_MOVE方法了(第一次时可能调用)。
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
final int xDiff = (int) Math.abs(mLastionMotionX - x);
//超过了最小滑动距离,可以响应滑动事件
if (xDiff > mTouchSlop) {
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
mLastionMotionX = x;
// mLastMotionY = y;
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}
public boolean onTouchEvent(MotionEvent event){
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
//TODO--此句好像没有影响
super.onTouchEvent(event);
//手指位置地点
float x = event.getX();
float y = event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//如果屏幕的动画还没结束,你就按下了,我们就结束该动画
if(mScroller != null){
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
}
mLastionMotionX = x ;
break ;
case MotionEvent.ACTION_MOVE:
//视图跟着手指的移动慢慢移动
int detaX = (int)(mLastionMotionX - x);
scrollBy(detaX, 0);
mLastionMotionX = x;
break ;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) mVelocityTracker.getXVelocity() ;
//滑动速率达到了一个标准(快速向右滑屏,返回上一个屏幕) 马上进行切屏处理
if (velocityX > SNAP_VELOCITY && curScreen > 0) {
// Fling enough to move left
snapToScreen(curScreen - 1);
}else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){
//快速向左滑屏,返回下一个屏幕)
snapToScreen(curScreen + 1);
}else{
//以上为快速移动的 ,强制切换屏幕
//我们是缓慢移动的,因此先判断是保留在本屏幕还是到下一屏幕
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mTouchState = TOUCH_STATE_REST ;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST ;
break;
}
return true ;
}
private void snapToDestination(){
//当前的偏移位置
int scrollX = getScrollX() ;
int scrollY = getScrollY() ;
//判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕
//直接使用这个公式判断是哪一个屏幕 前后或者自己
//判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕
// 这样的一个简单公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是
// 我们目标屏所在位置了。 假如每个屏幕宽度为320dip, 我们滑到了500dip处,很显然我们应该到达第二屏
int destScreen = (getScrollX() + getWidth() / 2 ) / getWidth() ;
snapToScreen(destScreen);
}
private void snapToScreen(int whichScreen){
//简单的移到目标屏幕,可能是当前屏或者下一屏幕
//直接跳转过去,不太友好
//scrollTo(mLastScreen * getWidth(), 0);
//为了友好性,我们在增加一个动画效果
//需要再次滑动的距离 屏或者下一屏幕的继续滑动距离
if(whichScreen > getChildCount() - 1){ //防止出界
whichScreen = getChildCount() - 1;
}
if (curScreen != whichScreen){
//切换到新的一个view
curScreen = whichScreen ;
//TODO----此处响应屏幕切换事件----------------
// Log.e(TAG, "snapToScreen,滑动:" + whichScreen);
if (null != mOnWebviewSwitchListener){
mOnWebviewSwitchListener.onWebviewSwitch(curScreen);
}
}
int dx = curScreen*getWidth() - getScrollX() ;
mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(dx) * 2);
//此时需要手动刷新View 否则没效果
invalidate();
}
/**
* 显示指定页面的webview
* @param whichWebview
*/
public void setDisplayedWebview(int whichWebview){
snapToScreen(whichWebview);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置该ViewGroup的大小
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 设置每个子视图的大小 , 即全屏
child.measure(getWidth(), MainActivity.scrrenHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int startLeft = 0; // 每个子视图的起始布局坐标
int startTop = 0; // 间距设置为10px 相当于 android:marginTop= "10px"
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//即使可见的,才划到屏幕上
if(child.getVisibility() != View.GONE){
child.layout(startLeft, startTop,
startLeft + getWidth(),
startTop + MainActivity.scrrenHeight );
}
startLeft = startLeft + getWidth() ; //校准每个子View的起始布局位置
//三个子视图的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960]
}
}
}
如果你看了我推荐的另一篇博文,那这个代码看起来不会很难,需要注意的是为了达到这样的效果:左右滑动效果不要太容易出现,因为当用户操作webview内部网页的时候也有一定的手势,很容易触发外部自定义gallery的左右滑动事件。在这里我们定一个整数mTouchSlop,使用该量来控制左右滑动效果触发的最低长度。这样左右滑动事件就不会很容易触发。