尝试写个UC浏览器(主页交互篇)

这是个看脸的时代,如果你没有个Beautiful Face(B脸),都不好意思写博客。继上一次我通宵加班(钥匙锁家里了,门也砸了)给大家介绍了UC浏览器基本布局的实现(轻点这里),帅气的我又来水文了。今天我们将实现UC浏览器主页的交互,No picture you say a ...

页面浏览(堆叠视图)
页面删除(堆叠视图)
上滑操作

下拉操作

相似度是不是很高O(∩_∩)O? 如果你喜欢或者想和我一起完成这个项目,请github一波(欢迎star):

https://github.com/zibuyuqing/UCBrowser

由于页面管理(堆叠视图)实现起来很复杂,我放到下一篇水文中。
这篇文章将按照以下顺序讲故事:

1.UCRootView简介(照顾没看过第一篇的同学:轻点这里
2.自定义基本父布局(BaseLayout)
3.网页导航栏(HeadLayout)视图更新
4.贝塞尔背景(BezierLayout)实现
5.底部菜单栏(Bottombar)视图更新
6.其他view简析

下面开始表演

UCRootView简介

UCRootView是这些可滑动布局(除了堆叠视图)的Parent,里面重写了onInterceptTouchEvent和onTouchEvent方法,定义了通用滑动接口:

    public interface ScrollStateListener{
        void onStartScroll();
        void onScroll(float rate);
        void onEndScroll();
        void onTouch(float x,float y);//手指位置
    }

滑动状态通过滑动距离与目标距离之间的比值rate来表示。在这个布局下,所有子布局的位置、大小,透明度等属性通过rate来控制。今天重点不是讲这个,如果想具体了解,请点这里

自定义基本父布局(BaseLayout)

布局关系图.png

从上图可以看出,BaseLayout是UC浏览器主页面所有View组件的父类,里面定义了基本的移位(translate,目前只针对Y方向)、缩放(scale)、渐变(alpha)等方法,所有的view的属性变化都是基于Rootview传过来的rate计算得到,子view通过扩展父类实现不同场景的界面切换效果。

移位

控制开关:mTranslateEnable
方向:Y
初始化:

    /**
     *
     * @param from 起始位置
     * @param to 最终位置
     */
    public void initTranslationY(int from, int to){
        mFromPosition = from;
        mToPosition = to;
        setTranslationY(from);
        mDistance = from - to;
    }

计算TransY:

    /**
     * 
     * @param rate 滑动的相对比率
     * @return
     */
    private float calculateTransY(float rate){
        Log.i(TAG,"rate :: =;" + rate);
        return mFromPosition + mDistance * rate;
    }

调用:在 onScroll(rate)方法中调用 setTranslationY(calculateTransY(rate));

缩放

控制开关:mScaleEnable
方向:X,Y
初始化:

    public void initScale(float startScale,float endScale){
        mStartScale = startScale;
        mEndScale = endScale;
        setScaleX(startScale);
        setScaleY(startScale);
        mScale = endScale - startScale;
    }

计算Scale:

    private float calculateScale(float rate){
        return mStartScale + mScale * rate;
    }

调用:

    private void setScaleXY(float rate){
        setScaleX(calculateScale(rate));
        setScaleY(calculateScale(rate));
    }

渐变同上,很简单。这样就把最基本的父布局写完了O(∩_∩)O哈哈~,然后我们就要写各个部件的更新啦。

网页导航栏(HeadLayout)视图更新

headlayout.png

这张图展示了UC浏览器HeadLayout布局结构,其中的BezierLayout包裹了天气栏、搜索栏、导航栏,其下方是网站列表,“刷新进入UC头条”提示默认不可见。现在打开手机UC浏览器,一起向上滑,预备,走!我们看到,当向上滑时,整个layout逐渐变小,并且小幅度向上移动,同时变黑,怎么实现这个效果呢?我们来分解一下:

变小 —— scale,上移 —— transY,变黑 —— foreground(黑色)改变Alpha渐显。

初始化
        mUCHeadLayout.setTranslateEnable(true);   // 可移动
        mUCHeadLayout.initTranslationY(0, -100);   // 小幅移动
开始移动

    @Override
    public void onStartScroll() {
        // mUCCoverLayout 为下拉时所看到的提示“上滑进入UC头条”的布局
        mUCCoverLayout.setVisibility(VISIBLE);
        mUCCoverLayout.setAlpha(0.f);
        super.onStartScroll();
    }

更新视图
@Override
    public void onScroll(float rate) {
        if(rate > 0) {
            // 下拉
            
            // 显示提示语并下移
            mCoverTip.setTranslationY(100 * Math.abs(rate));
            // 提示布局逐渐显现
            mUCCoverLayout.setAlpha(rate * 1.5f);
        } else {
            // 上滑
            
            // 隐藏提示布局
            mUCCoverLayout.setVisibility(GONE);

            // foreground 逐渐显现,布局变黑
            mForeground.setAlpha((int) (ALPHA_255 * Math.abs(rate)));
            float adjustRate = 1.0f + rate * 0.05f;
            
            // 布局内容逐渐变小
            mCategoryContain.setScaleX(adjustRate);
            mCategoryContain.setScaleY(adjustRate);
            mWebsiteContain.setScaleX(adjustRate);
            mWebsiteContain.setScaleY(adjustRate);
        }
        super.onScroll(rate);
    }

正如之前所说,整个过程受相对滑动比率rate控制,这样就实现了我们的HeadLayout界面更新,接下来我们看其下拉时背景效果实现。

贝塞尔背景(BezierLayout)实现

关于贝塞尔的相关知识感兴趣的可以百度一波哈。
我想告诉你ScrollStateListener接口中的onTouch(float x, float y)方法就是为了这货添加的,因为我们在开始构建贝塞尔曲线的时候要有控制点,总不能写死吧。当用户向下滑动时,我们动态的更新布局大小(Y方向),并把我们的控制点始终放在底部,贝塞尔曲线的起始点和终止点高度(Y)更新慢于控制点,就可以达到效果了。


贝塞尔.png
设置画笔
        mPaint = new Paint();
        mPaint.setColor(mThemeColor);
        mPaint.setAntiAlias(true); // 抗锯齿
        mPaint.setStyle(Paint.Style.FILL); // 填充
初始化位置
mEdgeHeight = mHeight; // mEdgeHeight 为左右两个边的高度
mControlPoint = new Point(0,mHeight); // 初始位置为整个视图的底部,这样一开始画出来是条直线
绘制贝塞尔区域
    @Override
    protected void dispatchDraw(Canvas canvas) {
        drawBg(canvas);
        super.dispatchDraw(canvas);
    }
    
    private void drawBg(Canvas canvas) {
        mPath.reset();
        // 顶部开始
        mPath.moveTo(0,0);
        mPath.lineTo(0,mEdgeHeight);
        // 贝塞尔曲线
        mPath.quadTo(mControlPoint.x,mControlPoint.y,mScreenWidth,mEdgeHeight);
        mPath.lineTo(mScreenWidth,0);
        // 闭合
        mPath.lineTo(0,0);
        canvas.drawPath(mPath,mPaint);
    }

这里用到的drawPath方法,大家可以详细了解一下Path的用法,很多炫酷效果是用这货弄的。

当手指下滑时,我们开始变弯了。

开始滑动并设置控制点
    @Override
    public void onStartScroll() {
        mStartScroll = true;
        super.onStartScroll();
    }
    public void touch(float x, float y) {
        mControlPoint.set((int) x, mControlPoint.y);
        invalidate();
    }
更新视图
@Override
    public void onScroll(float rate) {
        if(!mStartScroll){
            return;
        }
        // 获取 LayoutParams 根据滑动状态动态更新视图大小
        if(mLayoutParams == null){
            mLayoutParams = getLayoutParams();
        }
        if(rate >= 0) {
            // 下拉
            
            // FINAL_DISTANCE 为最大能滑动的距离
            int dis = (int) (FINAL_DISTANCE * rate);
            
            // 左右边界更新速度是控制点的0.5倍
            
            mEdgeHeight = (int) (mHeight + dis * 0.5f);
            
            //控制点更新
            mControlPoint.set(mControlPoint.x, mHeight + dis);
            
            // 视图内容改变大小,位置和透明度
            mContain.setScaleX(1.0f - rate * 0.2f);
            mContain.setScaleY(1.0f - rate * 0.2f);
            mContain.setTranslationY(dis * 0.5f);
            mContain.setAlpha(1.0f - rate * 1.5f);
        } else {
            // 上滑
            mControlPoint.set(0,mHeight);
        }
        // 改变视图大小
        mLayoutParams.height = mControlPoint.y;
        setLayoutParams(mLayoutParams);
        requestLayout();
        super.onScroll(rate);
    }

结束
    @Override
    public void onEndScroll() {
        mStartScroll = false;
        super.onEndScroll();
    }

上面注释的很详细,我们来看一下效果,不虚,不虚 O(∩_∩)O

底部菜单栏(Bottombar)视图更新

底部菜单栏视图更新原理和上面一样,只不过要根据menu位置计算更新速率,还有横向滑动。效果可以看前面的高清无码动图,这里我直接贴代码了。

计算中间Menu横向移动TransX
    private float calculateMenuBtnTransX(float rate){
        float dis = mScreenWidth / 5;
        return - dis * rate;
    }
计算新闻Menu竖向移动TransY

上滑时我们会看到新闻按钮(头条、视频、订阅)会根据不同速率依次滑动到指定位置,计算方法如下:

    private float calculateNewsBtnTransY(int finalY, float rate, float velocity){
 
        // velocity 是调整速率
        float adjustRate = rate * velocity;
        rate = adjustRate < -1.0f ? -1.0f : adjustRate;
        return 0 + finalY * rate;
    }
计算Menu透明度
    private float calculateBtnAlpha(float rate){
        return 1.0f + rate;
    }
更新视图
    @Override
    public void onScroll(float rate) {
        Log.(TAG,"onScroll :: rate =:" + rate);
        if(rate > 0){
            return;
        }
        //第1,2,4个按钮渐隐
        ivForward.setAlpha(calculateBtnAlpha(rate));
        ivBack.setAlpha(calculateBtnAlpha(rate));
        flWindowNum.setAlpha(calculateBtnAlpha(rate));
        // 第三个按钮移动到第四个位置
        ivMenu.setTranslationX(calculateMenuBtnTransX(rate));
        
        // 新闻按钮依次上升出现
        tvSubscribe.setTranslationY(calculateNewsBtnTransY(mHalfHeight,rate,1.0f));
        tvVideo.setTranslationY(calculateNewsBtnTransY(mHalfHeight,rate,1.5f));
        tvHeadline.setTranslationY(calculateNewsBtnTransY(mHalfHeight ,rate,2.0f));
    }

其他view简析

除了以上view组件,我们还要顶部搜索条(searchbar),新闻分类标签(newsTab)的更新没说呢,别急哈,很简单,用BaseLayout做这两个view组件的父布局,然后init一波就可以了,以searchbar为例

写布局
<?xml version="1.0" encoding="utf-8"?>
<com.zibuyuqing.ucbrowser.base.BaseLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="@color/themeBlue"
    android:padding="8dp"
    android:layout_height="@dimen/dimen_48dp">
    <ImageView
        android:layout_gravity="center"
        android:src="@drawable/ic_searchbar_book"
        style="@style/SearchBoxImageIconStyle"/>
    <TextView
        android:gravity="center"

        android:textSize="13dp"
        android:textColor="@color/windowBg"
        android:text="UC头条"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:background="@drawable/search_box_bg"
        android:padding="6dp"
        android:layout_marginLeft="8dp"
        android:layout_height="32dp">
        <ImageView
            android:layout_gravity="center_vertical"
            android:layout_width="14dp"
            android:layout_height="14dp"
            android:layout_alignParentStart="true"
            android:layout_marginLeft="8dp"
            android:src ="@drawable/ic_search" />
        <TextView
            android:textColor="@color/windowBg"
            android:gravity="center"
            android:layout_marginLeft="8dp"
            android:textSize="13dp"
            android:text="搜索你感兴趣的内容"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
    </LinearLayout>
</com.zibuyuqing.ucbrowser.base.BaseLayout>
初始化
       // 可移动
        mTopSearchBar.setTranslateEnable(true);

        // 这方法是在view layout 之后获取大小,避免获取的大小全是 0
        mTopSearchBar.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        mTopSearchBar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        // 初始化移动参数
                        mTopSearchBar.initTranslationY(-mTopSearchBar.getHeight(), 0);
                    }
                }
        );

大功告成,是不是很简单


说点其他的

文章写得很详细,能看到这个地方也算难为你了,给你点赞(也给我点个吧,顺手牵个赞O(∩_∩)O),如果你喜欢我的博客,请关注一下,我会不断将有意思的事情写出来,你也可以给我发一些需求,我来帮你实现或者给些建议,大家一起学习一起进步。
项目地址:https://github.com/zibuyuqing/UCBrowser,欢迎踩踏。、

下篇文章我们将探讨堆叠视图的实现,敬请期待

转载请注明:http://www.jianshu.com/p/af2dcf4f6522
上一篇:尝试写个UC浏览器(布局篇)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,514评论 25 707
  • 原文链接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影阅读 32,901评论 6 472
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,708评论 22 664
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,029评论 4 62
  • 2017年3月14号,请一辈子记住今天。 这一天,纹身的写实玫瑰没有做出来,我哭了。这一天,是吴辰到西安的第一天,...
    大喵了个咪阅读 178评论 0 0