撸一款全手势操作浏览器

作者: 大头呆 | 来源:发表于2017-12-27 17:28 被阅读77次

    晃晃时间过得真快,马上就到年底了。回首2017年也写了不少博客,其中不少是关于UI的。不久前写过一篇《仿夸克浏览器底部工具栏》,不过还是觉得有点不够,于是这次仿写下我觉得很好用的手势操作吧。银河系惯例,先上下效果图:

    scroll.gif

    可以看到整体非常简洁,除了标题栏其他全部是内容区域了

    可以看到前进、回退、回到主页、下拉刷新这些功能都是用手势来完成的,用过夸克浏览器的同学想必对此是很熟悉的。先来分析下页面构成:所有操作都在一个Activiy中,上面是两个Fragment:显示主页的Fragment和显示网页的Fragment,下面是标题栏。Fragment外面包裹了一个自定义的ViewGroup,用来实现手势操作,并把结果回调给Activity,并由Activity处理Fragment间切换和网页内切换的逻辑。接下来我们就开始着手实现这几个功能。

    手势操作ViewGroup

    这部分是用来捕获手势操作的组件,并把手势的操作回调给外层Activity,本身不处理任何功能逻辑。在上面的效果图我们可以看到,要实现这个效果,我们要处理的手势还是蛮复杂的,如果完全自己写,就要在onTouchEventonInterceptTouchEvent写很多的条件判断代码,而且需要自己去处理:事件冲突、边缘检测等。不仅代码量很大而且逻辑太复杂。所以这里我就又采用了自定义ViewGroup神器 -ViewDragHelper。在里面实现边界检测的功能。
    我们先来创建一个ViewDragHelper

    ViewDragHelper.create(this, 1.0f, new GestureDragCallback());
    

    然后定义需要检测的边界:

    mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_RIGHT | ViewDragHelper.EDGE_BOTTOM);
    

    因为下拉刷新是网页内自己的逻辑不涉及两个网页或两个Fragment,而且不需要触摸到边界才触发,所以这里只实现前进,后退和返回主页的三个边界手势。先来定义下需要回调的接口:

       public interface GestureListener {
        //开始触摸边界时回调,edgeFlags是边界的编号,返回值是否允许捕获这个手势
            boolean dragStartedEnable(int edgeFlags, ImageView view);
        //当图片移动大最大值时回调
            void onViewMaxPositionArrive(int edgeFlags, ImageView view);
       //当图片移动大最大值时然后手指松开时回调     
            void onViewMaxPositionReleased(int edgeFlags, ImageView view);
    
        }
    
    

    接下来就要实现GestureDragCallback里的内容了。这里我们以后退功能为例:

        private class GestureDragCallback extends ViewDragHelper.Callback {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                //mEdgeTrackerView禁止直接移动
                return false;
            }
    
            @Override
            public int clampViewPositionVertical(View child, int top, int dy){
                 //禁止竖直滑动,mLeftPos记录了leftRefreshView的初始纵坐标。
                 return mLeftPos.y;
            }
    
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                 //横向滑动范围就是自身宽度:-child.getWidth~0
                int leftBound = 0;
                int rightBound = 0;
                if (child == leftRefreshView) {
                    leftBound = -child.getWidth();
                }
                return Math.min(Math.max(left, leftBound), rightBound);
            }
            //在边界拖动时回调
            @Override
            public void onEdgeDragStarted(int edgeFlags, int pointerId) {
                if (mGestureListener != null && mGestureListener.dragStartedEnable(edgeFlags, leftRefreshView)) {
                    mViewDragHelper.captureChildView(leftRefreshView, pointerId);
                }
            }
            //拖动期间不断回调
            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                if (mGestureListener != null) {
                    if (changedView == leftRefreshView && leftRefreshView.getLeft() == 0) {
                       mGestureListener.onViewMaxPositionArrive(ViewDragHelper.EDGE_LEFT, leftRefreshView);
                    }
                }
            }
    
           //手指释放的时候回调
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    if (mGestureListener != null&&releasedChild == leftRefreshView) {
                        if (leftRefreshView.getLeft() == 0) {
                            mGestureListener.onViewMaxPositionReleased(ViewDragHelper.EDGE_LEFT, leftRefreshView);
                        }
                        //松手后回到原位置
                        mViewDragHelper.settleCapturedViewAt(mLeftPos.x, mLeftPos.y);
                        invalidate();
                    }
            }
        }
    

    其他两个功能也是类似的处理,这里就不再赘述了。

    Fragment间切换

    处理手势的自定义ViewGroup写好了,就可以把Fragment包起来了:

      <com.renny.simplebrowser.GestureLayout
            android:id="@+id/gesture_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="44dp">
    
            <FrameLayout
                android:id="@+id/container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
    
        </com.renny.simplebrowser.GestureLayout>
    

    中间的FrameLayout用来承载Fragment。上面提到过,我们需要两个Fragment:显示主页的HomePageFragment和显示网页的WebViewFragment

     private void goHomePage() {
            if (mHomePageFragment == null) {
                mHomePageFragment = new HomePageFragment();
                mHomePageFragment.setGoPageListener(new HomePageFragment.goPageListener() {
                    @Override
                    public void onGopage(String url) {
                        goWebView(url);
                    }
                });
                mFragmentManager.beginTransaction().add(R.id.container, mHomePageFragment).commit();
            } else {
                mFragmentManager.beginTransaction().replace(R.id.container,
                        mHomePageFragment).commit();
            }
            setTitle("主页");
            isOnHomePage = true;
        }
    
    private void goWebView(String url) {
            if (webViewFragment == null || !TextUtils.isEmpty(url)) {
                webViewFragment = new WebViewFragment();
                Bundle args = new Bundle();
                args.putString("url", url);
                webViewFragment.setArguments(args);
            }
            mFragmentManager.beginTransaction().replace(R.id.container,
                    webViewFragment).commit();
            isOnHomePage = false;
            WebView webView = webViewFragment.getWebView();
            if (webView != null) {
                setTitle(webView.getTitle());
            }
        }
    

    isOnHomePage用来标识是否在主页,如果在主页,那么返回主页功能就触发不了了。

    网页间切换

    这里就是用来处理WebView的前进后退了,主要用到WebView自己的方法:
    webView.canGoBack()webView.goBack()webView.canGoForward()webView.goForward()

    //回退
    private void returnLastPage() {
            if (fromBack && isOnHomePage) {
                goWebView(null);
            } else {
                WebView webView = webViewFragment.getWebView();
                if (webView.canGoBack()) {
                    webView.goBack();
                    setTitle(webView.getTitle());
                } else {
                    goHomePage();
                }
            }
        }
    //前进
        private void goNextPage() {
            WebView webView = webViewFragment.getWebView();
            if (webView.canGoForward()) {
                webView.goForward();
                setTitle(webView.getTitle());
            }
        }
    

    下拉刷新:

    这个就是单纯当前页面的操作了,只要将WebView包裹进一个下拉刷新的ViewGroup就行了,这里我采用了一个目前很流行的下拉刷新库:SmartRefreshLayout,当然你也完全可以替换成其它的。
    在下拉刷新开始时重载WebView

     refreshLayout.setOnRefreshListener(new OnRefreshListener() {
                @Override
                public void onRefresh(RefreshLayout refreshlayout) {
                    mWebView.reload();
                }
            });
    

    复写WebViewClient,在onPageFinished结束刷新:

        @Override
                public void onPageFinished(WebView webView, String s) {
                    super.onPageFinished(webView, s);
                    refreshLayout.finishRefresh();
                }
    
    

    感谢以下三方库:

    SmartRefreshLayout

    腾讯X5浏览器内核

    然后贴下本项目github地址:
    仿夸克浏览器手势控制

    相关文章

      网友评论

      本文标题:撸一款全手势操作浏览器

      本文链接:https://www.haomeiwen.com/subject/sqougxtx.html