Android 高仿華為手機Tab頁滑動導航效果

來源:互聯網
上載者:User

Android 高仿華為手機Tab頁滑動導航效果

首先帶大家看一下實現效果,用了兩種實現方式:
1.基於LinearLayout實現,導覽列不可響應手指滑動
2.基於HorizontalScrollView實現,導覽列可響應手指滑動

實現方式雖然不一樣,但是使用的是一樣的,因為我介面封裝的一模一樣,下面看實現效果。
基於LinearLayout的實現:

基於HorizontalScrollView的實現:

兩者效果一樣,區別就在於導航條可否隨使用者操作滑動。

下面只說明LinearLayout實現,HorizontalScrollView僅僅是套了一層LinearLayout,因為它只允許套一個子控制項。

需求開發點:
1.自訂控制項的編寫(通用方法:建構函式、init()、onMeasure、onLayout、onDraw、dispatchDraw)
2.白色圓點的位移量的計算
3.當Tab頁超過5個的時候,白點和LinearLayout內容的組合滑動,達到白點不動,而文字移動的效果。

實現這三點,這個控制項就算完事,當然一些細節方面的,可以根據自己的需求去調優。

1.自訂控制項的編寫
1) 這裡主要是onMeasure方法的複寫,不能用預設的onMeasure,需要我們根據Tab的數量去計算,是1-6個Tab的measure結果

這個就是根據Tab的數量平分螢幕的寬,當數量超過5個的時候,為了保持效果,只顯示5個,超出的內容將其追加到螢幕外面,螢幕是有界的,視圖是無界的,你可以無限往右邊添加,只要記憶體能容下,可以用個for迴圈無限添加看看發生什麼。

2) 複寫dispatchDraw方法,這裡dispatchDraw方法的作用就是畫一個白色的圓點。

白色圓點的位移量的計算
這個得靠ViewPager的OnPageChangeListener,通過它onPageScrolled的回調去計算,計算好了調用invalidate()去重繪。

當Tab頁超過5個的時候,白點和LinearLayout內容的組合滑動,達到白點不動,而文字移動的效果。
當滑動到第5個Tab,如果只是白色圓點移動就會移動到螢幕外面去,這個時候也需要讓LinearLayout內容聯動,通過scrollTo方法,往與白點相反的方向移動內容,使其和白點的移動達到平衡,這樣白點不動,上面的標題內容移動。

下面分析,寫該控制項需要的一些變數,先看:

1.需要白點是吧,我們用畫筆畫,所以Paint需要
2.白點畫多大?所以需要一個白點的半徑變數radius
3.白點的位移量,我們定義一個變數mOffset,根據ViewPager的滑動動態計算
4.上面還有一排文字,用TextView實現,這個TextView是根據Tab的數量動態添加的,數量不可控,我這裡並沒有定義成變數。但是文字內容對應的是一個Fragment的標題,也就是說標題和Fragment是一一對應的關係。OK用Map。

    /**     * 存放Tab的集合 Key:導覽列的標題 Fragment:導航頁     */    private Map mTabs;

TextView就是根據mTabs的數量動態添加的。
5.我們怎麼去計算滑動位移量呢?這個得靠ViewPager的OnPageChangeListener,通過它onPageScrolled的回調去計算mOffset,就是通過這種計算圓點的位移,這裡我持有了一個ViewPager的引用,所以有變數mViewPager,其實要不要關係不大.
6.螢幕最大可顯示的Tab數量MAX_VISIBLE_TAB_COUNTS,總不能有100個你就顯示100個吧.
7.實際螢幕上可顯示的Tab數量mRealVisibleTabCounts
8.螢幕寬度mScreenWidth輔助onMeasure計算的

下面是實現代碼:

package com.csm.hwtab;import java.util.ArrayList;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Cap;import android.graphics.Paint.Style;import android.support.v4.app.Fragment;import android.support.v4.view.ViewPager;import android.support.v4.view.ViewPager.OnPageChangeListener;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.TypedValue;import android.view.Gravity;import android.view.View;import android.view.View.OnClickListener;import android.widget.LinearLayout;import android.widget.TextView;/** * @author Rander.C */@SuppressLint("NewApi")public class TabLinearLayout extends LinearLayout implements OnClickListener, OnPageChangeListener {    /**     * 最大可顯示的Tab數,可寫成自訂屬性在xml裡面配置     */    private static final int MAX_VISIBLE_TAB_COUNTS = 5;    /**     * 實際可見的Tab數,比如有8個Tab,超過8個,為了保證效果,最多顯示5個     * 此時mRealVisibleTabCounts為5     * 如果只有3個tab小於5個,則mRealVisibleTabCounts為3     * 並根據這個去計算每個tab的寬度,螢幕寬度除以mRealVisibleTabCounts嘛     */    private int mRealVisibleTabCounts;    /**     * 小圓點滑動位移量     */    private float mOffset;    /**     * 繪製小圓點畫筆     */    private Paint mPaint;    /**     * 小圓點半徑     */    private int mCircleRadius;    /**     * 螢幕寬度     */    private int mScreenWidth;    /**     * 存放Tab的集合 Key:導覽列的標題 Fragment:導航頁     */    private Map mTabs;    /**     * View中有getContext()方法,用變數就會     * 多一個Context引用,會增加一丁點虛擬機器的負擔嗎?     * 如果不用變數,但是每次都使用getContext(),是不是都要多一層     * 調用棧,這個怎麼權衡,求答案,還是我想多了?     */    private Context mContext;    private ViewPager mViewPager;    public TabLinearLayout(Context context, AttributeSet attrs) {        this(context, attrs, -1);    }    public TabLinearLayout(Context context) {        this(context, null);    }    public TabLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        mContext = getContext();        setOrientation(HORIZONTAL);        mPaint = new Paint();        mPaint.setAntiAlias(true);        mPaint.setDither(true);        mPaint.setStyle(Style.FILL);        mPaint.setColor(Color.WHITE);        mPaint.setStrokeCap(Cap.ROUND);        DisplayMetrics metrics = getResources().getDisplayMetrics();        mScreenWidth = metrics.widthPixels;        mCircleRadius = (int) getResources().getDimension(R.dimen.radius);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int tabCount = getChildCount();        if (tabCount == 0) {            return;        }        /** 不管預設測量結果怎麼樣,我們都要重新給其寬度賦值*/        mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < tabCount ? MAX_VISIBLE_TAB_COUNTS : tabCount;        // 為每一個子view重新分配mScreenWidth / mRealVisibleTabCounts大小的寬度        int averageWidth = mScreenWidth / mRealVisibleTabCounts;        for (int i = 0; i < tabCount; i++) {            View view = getChildAt(i);            LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) view.getLayoutParams();            params.weight = 0;            params.width = averageWidth;            view.setLayoutParams(params);        }    }    /**     * 獲得Tabs列表     * @return     */    public List getTabs() {        return new ArrayList(mTabs.values());    }    /**     * 添加一個Tab項,添加一個tab的時候同時給     * 該視圖添加一個TextView     * @param title     * @param tab     */    public void addTab(String title, Fragment tab) {        if (mTabs == null) {            mTabs = new LinkedHashMap<>();        }        mTabs.put(title, tab);        addView(createTabItem(title, mTabs.size() - 1));    }    /**     * 設定ViewPager     * 同時給其設定OnPageChangeListener監聽器。     * @param viewPager     */    public void setViewPager(ViewPager viewPager) {        mViewPager = viewPager;        mViewPager.setOnPageChangeListener(this);    }    /**     * 建立一個Tab視圖     * @param title Fragment對應的標題     * @param index 該Fragment對應的索引下標     * @return 已經構造好的tab視圖     */    private View createTabItem(String title, int index) {        TextView tv_title = new TextView(mContext);        tv_title.setText(title);        tv_title.setTag(index);        tv_title.setGravity(Gravity.CENTER);        tv_title.setTextColor(Color.WHITE);        tv_title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);        tv_title.setOnClickListener(this);        return tv_title;    }    @Override    public void onClick(View v) {        int tabIndex = (Integer) v.getTag();        if (onTabOperatorListener != null) {            onTabOperatorListener.onTabClick(tabIndex);        }        mViewPager.setCurrentItem(tabIndex);    }    @Override    public void onPageScrollStateChanged(int state) {        if (null != onTabOperatorListener) {            onTabOperatorListener.onPageScrollStateChanged(state);        }    }    @Override    public void onPageScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels) {        mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < getChildCount() ? MAX_VISIBLE_TAB_COUNTS : getChildCount();        int tabWidth = getWidth() / mRealVisibleTabCounts;        // 計算小圓點的位移量,這個可以畫圖理解        mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );        // 如果滾動到最後一個,則同時要向左滾動tab內容,這種情況是:原點在向右移動,同時整體又向做滑動,兩個滑動相互抵消,相當於原點沒有移動        if (getChildCount() > mRealVisibleTabCounts && positionOffset > 0                && sourcePosition >= mRealVisibleTabCounts - 1) {            scrollTo((int) ((sourcePosition + 1 - mRealVisibleTabCounts) * tabWidth + tabWidth * positionOffset), 0);        }        invalidate();        if (null != onTabOperatorListener) {            onTabOperatorListener.onTabScrolled(sourcePosition, positionOffset, positionOffsetPixels);        }    }    @Override    public void onPageSelected(int index) {        if (null != onTabOperatorListener) {            onTabOperatorListener.onTabSelected(index);        }        /**         * 這裡不要多想,只是為了保證選中的tab為第一個時,讓布局重設到原點         */        if(index == 0)        {            scrollTo(0, 0);        }    }    /**     * Tab操作相關的監聽器     * @author rander     */    public interface OnTabOperatorListener {        void onTabClick(int tabIndex);        void onTabSelected(int tabIndex);        void onTabScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels);        void onPageScrollStateChanged(int state);    }    public OnTabOperatorListener onTabOperatorListener;    public void setOnTabItemClickListener(OnTabOperatorListener onTabOperatorListener) {        this.onTabOperatorListener = onTabOperatorListener;    }    @Override    protected void dispatchDraw(Canvas canvas) {        super.dispatchDraw(canvas);        //如果只有一個Tab則,不繪製圓點        if (getChildCount() <= 1) {            return;        }        //根據mOffset繪製位移量        canvas.drawCircle(mOffset, getHeight() - mCircleRadius * 2, mCircleRadius, mPaint);    }}

關於位移量的計算畫圖理解:
位移量計算的運算式:

mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );

圖解:

第一個豎線和第二個豎線:tabWidth / 2
第二個豎線和第三個豎線:sourcePosition * tabWidth
第三個豎線和第四個豎線:pZ喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vc2l0aW9uT2Zmc2V0ICogdGFiV2lkdGg8YnIgLz4NCtK7z+C8077NysfGq9LGwb/ByyzV4rj20qrV0rW9c291cmNlUG9zaXRpb266zXBvc2l0aW9uT2Zmc2V0tcS55sLJvs26w7zGy+PGq9LGwb/By6Osc2Nyb2xsVG+1xMar0sbBv9Kyv8nS1LLO1dXV4rj2wO294qGjPC9wPg0KPHA+vdPPwsC0vs3Kx8q508PByy48c3Ryb25nPsfzTGludXjPwrXEusO1xLutzby5pL7fPC9zdHJvbmc+PC9wPg0KPHA+yunQtLK8vtZ0YWIueG1so7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">

在Activity中使用:

package com.csm.hwtab;import com.csm.hwtab.adapter.MyFragmentPagerAdapter;import com.csm.hwtab.fragment.OneFragment;import android.os.Bundle;import android.support.v4.app.FragmentActivity;import android.support.v4.view.ViewPager;import android.view.Window;public class TestActivity extends FragmentActivity{    private ViewPager mViewPager;    private TabLinearLayout mTabview;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        requestWindowFeature(Window.FEATURE_NO_TITLE);        setContentView(R.layout.tab);        initTabsView();    }    private void initTabsView() {        mViewPager = (ViewPager) findViewById(R.id.view_pager);        mTabview = (TabLinearLayout)findViewById(R.id.tab);        mTabview.addTab("先秦0", new OneFragment());        mTabview.addTab("先秦1", new OneFragment());        mTabview.addTab("先秦2", new OneFragment());        mTabview.addTab("先秦3", new OneFragment());        mTabview.addTab("先秦4", new OneFragment());        mTabview.addTab("先秦5", new OneFragment());        mTabview.addTab("先秦6", new OneFragment());        mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), mTabview.getTabs()));        mTabview.setViewPager(mViewPager);        mViewPager.setCurrentItem(0);    }}

效果就如同上面的效果一樣了。

github:https://github.com/shuangmin/HwTab

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.