Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 高仿華為手機Tab頁滑動導航效果

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

編輯:關於Android編程

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

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

基於HorizontalScrollView的實現:
基於HorizontalScrollView實現的滑動導航

兩者效果一樣,區別就在於導航條可否隨用戶操作滑動。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs/Cw+bWu8u1w/dMaW5lYXJMYXlvdXTKtc/Wo6xIb3Jpem9udGFsU2Nyb2xsVmlld732vfbKx8zXwcvSu7LjTGluZWFyTGF5b3V0o6zS8s6qy/zWu9TK0O3M19K7uPbX07/YvP6hozwvcD4NCjxwPtDox/O/qreiteOjujxiciAvPg0KMS7X1Lao0uW/2Lz+tcSx4NC0o6jNqNPDt723qKO6ubnU7Lqvyv2homluaXQoKaGib25NZWFzdXJloaJvbkxheW91dKGib25EcmF3oaJkaXNwYXRjaERyYXejqTxiciAvPg0KMi6w18mr1LK147XExqvSxsG/tcS8xsvjPGJyIC8+DQozLrWxVGFi0rOzrLn9o7W49rXEyrG68qOssNe147rNTGluZWFyTGF5b3V0xNrI3bXE1+m6z7ustq+jrLTvtb2w17Xjsru2r6OstvjOxNfW0sa2r7XE0Ke5+6GjPC9wPg0KPHA+yrXP1tXiyP2146Os1eK49r/YvP6+zcvjzerKwqOstbHIu9K70KnPuL3at73D5rXEo6y/ydLUuPm+3dfUvLq1xNDox/PIpbX308WhozwvcD4NCjxwPjxzdHJvbmc+MS7X1Lao0uW/2Lz+tcSx4NC0PC9zdHJvbmc+PGJyIC8+DQqjsSkg1eLA79b30qrKx29uTWVhc3VyZbe9t6i1xLi00LSjrLK7xNzTw8SsyM+1xG9uTWVhc3VyZaOs0OjSqs7Sw8e4+b7dVGFitcTK/cG/yKW8xsvjo6zPws28ysejsaOto7a49lRhYrXEbWVhc3VyZb3hufs8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160405/20160405094158445.png" title="\" />
這個就是根據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
第三個豎線和第四個豎線:positionOffset * tabWidth
一相加就是偏移量了,這個要找到sourcePosition和positionOffset的規律就好計算偏移量了,scrollTo的偏移量也可以參照這個理解。

接下來就是使用了.求Linux下的好的畫圖工具

書寫布局tab.xml:




    
    

    

在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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved