嘗試寫個UC瀏覽器(主頁互動篇)

子不語卿發表於2017-12-08

這是個看臉的時代,如果你沒有個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

嘗試寫個UC瀏覽器(主頁互動篇)

底部選單欄(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);
                    }
                }
        );
複製程式碼

大功告成,是不是很簡單

嘗試寫個UC瀏覽器(主頁互動篇)

說點其他的

文章寫得很詳細,能看到這個地方也算難為你了,給你點贊(也給我點個吧,順手牽個贊O(∩_∩)O),如果你喜歡我的部落格,請關注一下,我會不斷將有意思的事情寫出來,你也可以給我發一些需求,我來幫你實現或者給些建議,大家一起學習一起進步。 專案地址:github.com/zibuyuqing/…,歡迎踩踏。

下篇文章我們將探討堆疊檢視的實現,敬請期待

轉載請註明:juejin.im/post/5a291a…

上一篇:嘗試寫個UC瀏覽器(佈局篇)

相關文章