嘗試寫個(gè)UC瀏覽器(主頁交互篇)

這是個(gè)看臉的時(shí)代狞换,如果你沒有個(gè)Beautiful Face(B臉),都不好意思寫博客。繼上一次我通宵加班(鑰匙鎖家里了低斋,門也砸了)給大家介紹了UC瀏覽器基本布局的實(shí)現(xiàn)(輕點(diǎn)這里),帥氣的我又來水文了匪凡。今天我們將實(shí)現(xiàn)UC瀏覽器主頁的交互膊畴,No picture you say a ...

頁面瀏覽(堆疊視圖)
頁面刪除(堆疊視圖)
上滑操作

下拉操作

相似度是不是很高O(∩_∩)O? 如果你喜歡或者想和我一起完成這個(gè)項(xiàng)目,請(qǐng)github一波(歡迎star):

https://github.com/zibuyuqing/UCBrowser

由于頁面管理(堆疊視圖)實(shí)現(xiàn)起來很復(fù)雜病游,我放到下一篇水文中唇跨。
這篇文章將按照以下順序講故事:

1.UCRootView簡(jiǎn)介(照顧沒看過第一篇的同學(xué):輕點(diǎn)這里
2.自定義基本父布局(BaseLayout)
3.網(wǎng)頁導(dǎo)航欄(HeadLayout)視圖更新
4.貝塞爾背景(BezierLayout)實(shí)現(xiàn)
5.底部菜單欄(Bottombar)視圖更新
6.其他view簡(jiǎn)析

下面開始表演

UCRootView簡(jiǎn)介

UCRootView是這些可滑動(dòng)布局(除了堆疊視圖)的Parent,里面重寫了onInterceptTouchEvent和onTouchEvent方法衬衬,定義了通用滑動(dòng)接口:

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

滑動(dòng)狀態(tài)通過滑動(dòng)距離與目標(biāo)距離之間的比值rate來表示买猖。在這個(gè)布局下,所有子布局的位置滋尉、大小玉控,透明度等屬性通過rate來控制。今天重點(diǎn)不是講這個(gè)狮惜,如果想具體了解高诺,請(qǐng)點(diǎn)這里制圈。

自定義基本父布局(BaseLayout)

布局關(guān)系圖.png

從上圖可以看出膨报,BaseLayout是UC瀏覽器主頁面所有View組件的父類官辽,里面定義了基本的移位(translate纳账,目前只針對(duì)Y方向)甘磨、縮放(scale)烦周、漸變(alpha)等方法帚稠,所有的view的屬性變化都是基于Rootview傳過來的rate計(jì)算得到孵运,子view通過擴(kuò)展父類實(shí)現(xiàn)不同場(chǎng)景的界面切換效果。

移位

控制開關(guān):mTranslateEnable
方向:Y
初始化:

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

計(jì)算TransY:

    /**
     * 
     * @param rate 滑動(dòng)的相對(duì)比率
     * @return
     */
    private float calculateTransY(float rate){
        Log.i(TAG,"rate :: =;" + rate);
        return mFromPosition + mDistance * rate;
    }

調(diào)用:在 onScroll(rate)方法中調(diào)用 setTranslationY(calculateTransY(rate));

縮放

控制開關(guān):mScaleEnable
方向:X诅迷,Y
初始化:

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

計(jì)算Scale:

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

調(diào)用:

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

漸變同上佩番,很簡(jiǎn)單。這樣就把最基本的父布局寫完了O(∩_∩)O哈哈~罢杉,然后我們就要寫各個(gè)部件的更新啦趟畏。

網(wǎng)頁導(dǎo)航欄(HeadLayout)視圖更新

headlayout.png

這張圖展示了UC瀏覽器HeadLayout布局結(jié)構(gòu),其中的BezierLayout包裹了天氣欄滩租、搜索欄赋秀、導(dǎo)航欄,其下方是網(wǎng)站列表律想,“刷新進(jìn)入U(xiǎn)C頭條”提示默認(rèn)不可見×粤現(xiàn)在打開手機(jī)UC瀏覽器,一起向上滑技即,預(yù)備著洼,走!我們看到而叼,當(dāng)向上滑時(shí)身笤,整個(gè)layout逐漸變小,并且小幅度向上移動(dòng)葵陵,同時(shí)變黑液荸,怎么實(shí)現(xiàn)這個(gè)效果呢?我們來分解一下:

變小 —— scale脱篙,上移 —— transY娇钱,變黑 —— foreground(黑色)改變Alpha漸顯。

初始化
        mUCHeadLayout.setTranslateEnable(true);   // 可移動(dòng)
        mUCHeadLayout.initTranslationY(0, -100);   // 小幅移動(dòng)
開始移動(dòng)

    @Override
    public void onStartScroll() {
        // mUCCoverLayout 為下拉時(shí)所看到的提示“上滑進(jìn)入U(xiǎn)C頭條”的布局
        mUCCoverLayout.setVisibility(VISIBLE);
        mUCCoverLayout.setAlpha(0.f);
        super.onStartScroll();
    }

更新視圖
@Override
    public void onScroll(float rate) {
        if(rate > 0) {
            // 下拉
            
            // 顯示提示語并下移
            mCoverTip.setTranslationY(100 * Math.abs(rate));
            // 提示布局逐漸顯現(xiàn)
            mUCCoverLayout.setAlpha(rate * 1.5f);
        } else {
            // 上滑
            
            // 隱藏提示布局
            mUCCoverLayout.setVisibility(GONE);

            // foreground 逐漸顯現(xiàn)绊困,布局變黑
            mForeground.setAlpha((int) (ALPHA_255 * Math.abs(rate)));
            float adjustRate = 1.0f + rate * 0.05f;
            
            // 布局內(nèi)容逐漸變小
            mCategoryContain.setScaleX(adjustRate);
            mCategoryContain.setScaleY(adjustRate);
            mWebsiteContain.setScaleX(adjustRate);
            mWebsiteContain.setScaleY(adjustRate);
        }
        super.onScroll(rate);
    }

正如之前所說文搂,整個(gè)過程受相對(duì)滑動(dòng)比率rate控制,這樣就實(shí)現(xiàn)了我們的HeadLayout界面更新秤朗,接下來我們看其下拉時(shí)背景效果實(shí)現(xiàn)细疚。

貝塞爾背景(BezierLayout)實(shí)現(xiàn)

關(guān)于貝塞爾的相關(guān)知識(shí)感興趣的可以百度一波哈。
我想告訴你ScrollStateListener接口中的onTouch(float x, float y)方法就是為了這貨添加的川梅,因?yàn)槲覀冊(cè)陂_始構(gòu)建貝塞爾曲線的時(shí)候要有控制點(diǎn),總不能寫死吧然遏。當(dāng)用戶向下滑動(dòng)時(shí)贫途,我們動(dòng)態(tài)的更新布局大小(Y方向),并把我們的控制點(diǎn)始終放在底部待侵,貝塞爾曲線的起始點(diǎn)和終止點(diǎn)高度(Y)更新慢于控制點(diǎn)丢早,就可以達(dá)到效果了。


貝塞爾.png
設(shè)置畫筆
        mPaint = new Paint();
        mPaint.setColor(mThemeColor);
        mPaint.setAntiAlias(true); // 抗鋸齒
        mPaint.setStyle(Paint.Style.FILL); // 填充
初始化位置
mEdgeHeight = mHeight; // mEdgeHeight 為左右兩個(gè)邊的高度
mControlPoint = new Point(0,mHeight); // 初始位置為整個(gè)視圖的底部,這樣一開始畫出來是條直線
繪制貝塞爾區(qū)域
    @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方法怨酝,大家可以詳細(xì)了解一下Path的用法傀缩,很多炫酷效果是用這貨弄的。

當(dāng)手指下滑時(shí)农猬,我們開始變彎了赡艰。

開始滑動(dòng)并設(shè)置控制點(diǎn)
    @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 根據(jù)滑動(dòng)狀態(tài)動(dòng)態(tài)更新視圖大小
        if(mLayoutParams == null){
            mLayoutParams = getLayoutParams();
        }
        if(rate >= 0) {
            // 下拉
            
            // FINAL_DISTANCE 為最大能滑動(dòng)的距離
            int dis = (int) (FINAL_DISTANCE * rate);
            
            // 左右邊界更新速度是控制點(diǎn)的0.5倍
            
            mEdgeHeight = (int) (mHeight + dis * 0.5f);
            
            //控制點(diǎn)更新
            mControlPoint.set(mControlPoint.x, mHeight + dis);
            
            // 視圖內(nèi)容改變大小,位置和透明度
            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);
    }

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

上面注釋的很詳細(xì)斤葱,我們來看一下效果慷垮,不虛,不虛 O(∩_∩)O

底部菜單欄(Bottombar)視圖更新

底部菜單欄視圖更新原理和上面一樣揍堕,只不過要根據(jù)menu位置計(jì)算更新速率料身,還有橫向滑動(dòng)。效果可以看前面的高清無碼動(dòng)圖衩茸,這里我直接貼代碼了芹血。

計(jì)算中間Menu橫向移動(dòng)TransX
    private float calculateMenuBtnTransX(float rate){
        float dis = mScreenWidth / 5;
        return - dis * rate;
    }
計(jì)算新聞Menu豎向移動(dòng)TransY

上滑時(shí)我們會(huì)看到新聞按鈕(頭條、視頻楞慈、訂閱)會(huì)根據(jù)不同速率依次滑動(dòng)到指定位置幔烛,計(jì)算方法如下:

    private float calculateNewsBtnTransY(int finalY, float rate, float velocity){
 
        // velocity 是調(diào)整速率
        float adjustRate = rate * velocity;
        rate = adjustRate < -1.0f ? -1.0f : adjustRate;
        return 0 + finalY * rate;
    }
計(jì)算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個(gè)按鈕漸隱
        ivForward.setAlpha(calculateBtnAlpha(rate));
        ivBack.setAlpha(calculateBtnAlpha(rate));
        flWindowNum.setAlpha(calculateBtnAlpha(rate));
        // 第三個(gè)按鈕移動(dòng)到第四個(gè)位置
        ivMenu.setTranslationX(calculateMenuBtnTransX(rate));
        
        // 新聞按鈕依次上升出現(xiàn)
        tvSubscribe.setTranslationY(calculateNewsBtnTransY(mHalfHeight,rate,1.0f));
        tvVideo.setTranslationY(calculateNewsBtnTransY(mHalfHeight,rate,1.5f));
        tvHeadline.setTranslationY(calculateNewsBtnTransY(mHalfHeight ,rate,2.0f));
    }

其他view簡(jiǎn)析

除了以上view組件,我們還要頂部搜索條(searchbar)抖部,新聞分類標(biāo)簽(newsTab)的更新沒說呢说贝,別急哈,很簡(jiǎn)單慎颗,用BaseLayout做這兩個(gè)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="搜索你感興趣的內(nèi)容"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
    </LinearLayout>
</com.zibuyuqing.ucbrowser.base.BaseLayout>
初始化
       // 可移動(dòng)
        mTopSearchBar.setTranslateEnable(true);

        // 這方法是在view layout 之后獲取大小,避免獲取的大小全是 0
        mTopSearchBar.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        mTopSearchBar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        // 初始化移動(dòng)參數(shù)
                        mTopSearchBar.initTranslationY(-mTopSearchBar.getHeight(), 0);
                    }
                }
        );

大功告成俯萎,是不是很簡(jiǎn)單


說點(diǎn)其他的

文章寫得很詳細(xì)傲宜,能看到這個(gè)地方也算難為你了,給你點(diǎn)贊(也給我點(diǎn)個(gè)吧夫啊,順手牽個(gè)贊O(∩_∩)O)函卒,如果你喜歡我的博客,請(qǐng)關(guān)注一下撇眯,我會(huì)不斷將有意思的事情寫出來报嵌,你也可以給我發(fā)一些需求,我來幫你實(shí)現(xiàn)或者給些建議熊榛,大家一起學(xué)習(xí)一起進(jìn)步锚国。
項(xiàng)目地址:https://github.com/zibuyuqing/UCBrowser,歡迎踩踏玄坦。血筑、

下篇文章我們將探討堆疊視圖的實(shí)現(xiàn)绘沉,敬請(qǐng)期待

轉(zhuǎn)載請(qǐng)注明:http://www.reibang.com/p/af2dcf4f6522
上一篇:嘗試寫個(gè)UC瀏覽器(布局篇)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市豺总,隨后出現(xiàn)的幾起案子车伞,更是在濱河造成了極大的恐慌,老刑警劉巖喻喳,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件另玖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沸枯,警方通過查閱死者的電腦和手機(jī)日矫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绑榴,“玉大人哪轿,你說我怎么就攤上這事∠柙酰” “怎么了窃诉?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赤套。 經(jīng)常有香客問我飘痛,道長(zhǎng),這世上最難降的妖魔是什么容握? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任宣脉,我火速辦了婚禮,結(jié)果婚禮上剔氏,老公的妹妹穿的比我還像新娘塑猖。我一直安慰自己,他們只是感情好谈跛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布羊苟。 她就那樣靜靜地躺著,像睡著了一般感憾。 火紅的嫁衣襯著肌膚如雪蜡励。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天阻桅,我揣著相機(jī)與錄音凉倚,去河邊找鬼。 笑死嫂沉,一個(gè)胖子當(dāng)著我的面吹牛稽寒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播输瓜,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了尤揣?” 一聲冷哼從身側(cè)響起搔啊,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎北戏,沒想到半個(gè)月后负芋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗜愈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年旧蛾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蠕嫁。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锨天,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剃毒,到底是詐尸還是另有隱情病袄,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布赘阀,位于F島的核電站益缠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏基公。R本人自食惡果不足惜幅慌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轰豆。 院中可真熱鬧胰伍,春花似錦、人聲如沸秒咨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雨席。三九已至菩咨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陡厘,已是汗流浹背抽米。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糙置,地道東北人云茸。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谤饭,于是被迫代替她去往敵國(guó)和親标捺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子懊纳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評(píng)論 25 707
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,929評(píng)論 6 472
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,754評(píng)論 22 665
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件亡容、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,093評(píng)論 4 62
  • 2017年3月14號(hào)嗤疯,請(qǐng)一輩子記住今天。 這一天闺兢,紋身的寫實(shí)玫瑰沒有做出來茂缚,我哭了。這一天屋谭,是吳辰到西安的第一天脚囊,...
    大喵了個(gè)咪閱讀 179評(píng)論 0 0