Android-自定義TabHost

效果圖:android

 

佈局代碼相關:ide

<!-- 自定義簡單的TabHost選項卡 --> 
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myswitch="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".upgrade.MainActivity">

    <!--<custom.view.upgrade.my_tab_host.TabViewHead
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="@color/ring_test1"/>-->

    <!-- 模型 -->
    <!--<View
        android:layout_width="145px"
        android:layout_height="20px"
        android:background="@drawable/scroller_line"/>-->

    <!--<View
        android:layout_width="145px"
        android:layout_height="20px"
        android:background="@drawable/scroller_rectangle"/>-->

    <!-- 控制器父類,控制 頭部 和 內容 ViewGroup -->
    <custom.view.upgrade.my_tab_host.TabFatherControlViewGroup
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 頭部 ViewGroup -->
        <custom.view.upgrade.my_tab_host.TabViewHeadGroup
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/ring_test1">

            <View
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/scroller_rectangle"/>

            <TextView
                android:id="@+id/tv_title1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:textColor="@android:color/black"
                android:text="首頁一"
                />

            <TextView
                android:id="@+id/tv_title2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:textColor="@android:color/black"
                android:text="首頁二"
                />

        </custom.view.upgrade.my_tab_host.TabViewHeadGroup>

        <!-- 內容體 ViewGroup -->
        <custom.view.upgrade.my_tab_host.TabViewContentGroup
            android:id="@+id/tab_view_content_group"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#33f00000"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="我是第一個頁面"
                    android:textColor="@android:color/black"/>

            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#33ffff00"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="我是第二頁面"
                    android:textColor="@android:color/black"/>

            </LinearLayout>

        </custom.view.upgrade.my_tab_host.TabViewContentGroup>

        <!-- 藍色滑動條,用於動態更改 -->
        <View
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/scroller_blue_rectangle" />

    </custom.view.upgrade.my_tab_host.TabFatherControlViewGroup>

</LinearLayout>

 

顏色相關:佈局

    <color name="ring_test1">#BED887</color>
    <color name="ring_test2">#F53D4D</color>
    <color name="ring_test3">#ECBBB9</color>

 

紅色滑動條 shape :post

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">

    <solid android:color="#f00" />

    <size android:width="245px" android:height="20px" />

</shape>

 

藍色滑動條 shape:測試

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">

    <solid android:color="@android:color/holo_blue_bright" />

    <size android:width="245px" android:height="17px" />

</shape>

 

定義的接口回調:動畫

package custom.view.upgrade.my_tab_host;

/**
 * 用於滑動內容Content去回調藍色滑動條的變化接口標準
 */
public interface ICallbackBlueRectangle {

    /**
     * Content觸摸Move的值
     * @param thisScrollX 當前的ScrollX值
     * @param moveValue 須要移動多少的值
     */
    public void callbackMoveValue(int thisScrollX, int moveValue);

    /**
     * 移動到左邊
     * @param thisScrollX 當前的ScrollX值
     */
    public void callbackMoveLeft(int thisScrollX);

    /**
     * 移動到右邊
     * @param thisScrollX 當前的ScrollX值
     */
    public void callbackMoveRight(int thisScrollX);
}
package custom.view.upgrade.my_tab_host;

/**
 * 此接口用於會回調自定義TabHost內容體動做
 */
public interface ICallbackContent {
    
    public void callbacToLeftContent();

    public void callbackToRightContent();

}
package custom.view.upgrade.my_tab_host;

/**
 * 用於回調自定義Head標題
 */
public interface ICallbackHead {

    public void callbackToLeftHead();

    public void callbackToRightHead();

}

 

最外層的 ViewGroup,須要管理好三個子控件:this

TabFatherControlViewGroup
package custom.view.upgrade.my_tab_host;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

public class TabFatherControlViewGroup extends ViewGroup implements ICallbackBlueRectangle, ICallbackContent {

    private final String TAG = TabFatherControlViewGroup.class.getSimpleName();

    /**
     * Xml佈局使用的構造方法
     * @param context
     * @param attrs
     */
    public TabFatherControlViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 定義頭部ViewGroup:重要的第一個孩子
     */
    private TabViewHeadGroup tabViewHeadGroup;

    /**
     * 定義內容ViewGroup:重要的第二個孩子 ViewGroup的父類是View,ViewGroup又能夠包含是View
     */
    private TabViewContentGroup tabViewContentGroup;

    /**
     * 定義第三個子控件View,是個藍色的滑動條
     */
    private View blueRectangleView;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        tabViewHeadGroup = (TabViewHeadGroup) getChildAt(0);

        // 測試ID獲取
        // tabViewContentGroup = findViewById(R.id.tab_view_content_group); // 若是和TabViewContent是同級,是獲取不到的
        tabViewContentGroup = (TabViewContentGroup) getChildAt(1);
        Log.d(TAG, "onFinishInflate() ---->" + tabViewContentGroup);

        blueRectangleView = getChildAt(2);

        bindToContent();
    }

    /**
     * Head去綁定Content
     */
    private void bindToContent() {
        if (null != tabViewHeadGroup && null != tabViewContentGroup) {
            tabViewHeadGroup.setCallbackContent(tabViewContentGroup.implementContent());
            tabViewHeadGroup.setiCallbackContent2(this);
            bindToHead();
            bindContentToThis();
        }
    }

    /**
     * Content去綁定Head
     */
    private void bindToHead() {
        tabViewContentGroup.setCallbackHead(tabViewHeadGroup.implementHead());
    }

    /**
     * 本身與Content創建綁定關係
     */
    private void bindContentToThis() {
        tabViewContentGroup.setCallbackBlueRectangle(this);
    }

    /**
     * 定義自身的值
     */
    private int thisViewWidth;
    private int thisViewHeight;

    private int thisViewWidthMode;
    private int thisViewHeightMode;

    /**
     * 測量方法,用於測量子控件的高寬
     * @param widthMeasureSpec 由父控件LinearLayout通過一些列計算傳遞過來的值
     * @param heightMeasureSpec 由父控件LinearLayout通過一些列計算傳遞過來的值
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        thisViewWidth = MeasureSpec.getSize(widthMeasureSpec);
        thisViewHeight = MeasureSpec.getSize(heightMeasureSpec);

        thisViewWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        thisViewHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        // 判斷頭部子控件設置的屬性,進行判斷
        int tabViewHeadGroupWidth = tabViewHeadGroup.getLayoutParams().width;
        int tabViewHeadGroupHeight = tabViewHeadGroup.getLayoutParams().height;

        if (tabViewHeadGroupWidth == LayoutParams.WRAP_CONTENT) {
            throw new IllegalArgumentException("error tabViewHeadGroup width 不能設置爲wrap_content,請修正");
        } else if (thisViewWidthMode != MeasureSpec.AT_MOST && tabViewHeadGroupWidth == LayoutParams.MATCH_PARENT) {
            // 把自身控件測量的寬度,給子控件
            tabViewHeadGroupWidth = getMeasuredWidth();
        }

        if (tabViewHeadGroupHeight == LayoutParams.WRAP_CONTENT) {
            throw new IllegalArgumentException("error tabViewHeadGroup height not set wrap_content");
        } else if (thisViewHeightMode != MeasureSpec.AT_MOST && tabViewHeadGroupHeight == LayoutParams.MATCH_PARENT) {
            // 把自身控件測量後的高度,傳給子控件
            tabViewHeadGroupHeight = getMeasuredHeight();
        }

        tabViewHeadGroup.measure(tabViewHeadGroupWidth, tabViewHeadGroupHeight);

        // ----
        // 判斷內容子控件設置的屬性,進行判斷
        int tabViewContentGroupWidth = tabViewContentGroup.getLayoutParams().width;
        int tabVIewContentGroupHeight = tabViewContentGroup.getLayoutParams().height;

        // 若是當前本身不是精確值模式,而且,子控件是LayoutParams.MATCH_PARENT,就把當前本身的寬高值傳給子控件
        if (tabViewContentGroupWidth == LayoutParams.WRAP_CONTENT) {
            throw new IllegalArgumentException("error tabViewContentGroup width height not set wrap_content");
        } else if (thisViewWidthMode != MeasureSpec.AT_MOST && tabViewContentGroupWidth == LayoutParams.MATCH_PARENT) {
            // 把自身的寬度給子控件
            tabViewContentGroupWidth = thisViewWidth;
        }

        if (tabVIewContentGroupHeight == LayoutParams.WRAP_CONTENT) {
            throw new IllegalArgumentException("error tabViewContentGroup height height not set wrap_content");
        } else if (thisViewHeightMode != MeasureSpec.AT_MOST && tabVIewContentGroupHeight == LayoutParams.MATCH_PARENT){
            // 把自身的高度給子控件
            tabVIewContentGroupHeight = getMeasuredHeight();
        }

        // 測量TabViewContentGroup, 寬高就用在佈局中設置的match_parent
        // Toast.makeText(getContext(), "" + tabViewContentGroup.getLayoutParams().width + "  " + tabViewContentGroup.getLayoutParams().height, Toast.LENGTH_LONG).show();
        tabViewContentGroup.measure(tabViewContentGroupWidth, tabVIewContentGroupHeight);
        // 測試測量傳遞固定值200
        // tabViewContentGroup.measure(200, 200);

        // 注意:thisViewWidth 和 getMeasuredWidth 是同樣的,都是父控件通過一些列處理獲得的值
        Log.d(TAG, "setMeasuredDimension前 thisViewWidth:" + thisViewWidth + " getMeasuredWidth():" + getMeasuredWidth() + " thisViewWidthMode:" + thisViewWidthMode);
        setMeasuredDimension(thisViewWidth, thisViewHeight);
        Log.d(TAG, "setMeasuredDimension後 getMeasuredWidth():" + getMeasuredWidth() + " thisViewWidthMode:" + thisViewWidthMode);
        // thisViewidthMode = 1073741824

        blueRectangleView.measure(blueRectangleView.getLayoutParams().width, blueRectangleView.getLayoutParams().height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int tabViewContentWidth = tabViewContentGroup.getMeasuredWidth();
        int tabViewContentHeight = tabViewContentGroup.getMeasuredHeight();
        // 若是是0,就表明測量有問題
        Log.d(TAG, "tabViewContentWidth:" + tabViewContentWidth + " tabViewContentHeight:" + tabViewContentHeight);

        // 給TabViewHeadGroup頭部固定好位置
        tabViewHeadGroup.layout(0,
                                  0,
                                   getMeasuredWidth(), // 自身TabFatherControlView寬度
                                   tabViewHeadGroup.getMeasuredHeight());

        // 給TabViewContentGroup固定好位置
        tabViewContentGroup.layout(0,
                tabViewHeadGroup.getMeasuredHeight(),
                thisViewWidth,
                thisViewHeight + tabViewHeadGroup.getMeasuredHeight());

        // 給藍色滑動條固定位置,排版
        blueRectangleView.layout(
                                 TabViewHeadGroup.LEFT_RIGHT,
                                tabViewHeadGroup.getMeasuredHeight() + 10,
                                blueRectangleView.getMeasuredWidth() + TabViewHeadGroup.LEFT_RIGHT,
                                tabViewHeadGroup.getMeasuredHeight() + blueRectangleView.getMeasuredHeight() + 10);

        // Test
        // animatorMove(1000, 0, 200, DIRECTION.RIGHT);
    }

    /**
     * 動畫移動藍色滑動條
     */
    private void animatorMove(int duration, float startX, float stopX /*, DIRECTION direction*/) {
        /*
        float values1 = 0f;
        float values2 = 0f;

        if (direction == DIRECTION.RIGHT) {
            values1 = startX;
            values2 = stopX;
        } else if (direction == DIRECTION.LEFT) {
            values1 = stopX;
            values2 = startX;
        }*/

        ObjectAnimator.ofFloat(blueRectangleView,
                               "translationX",
                                startX,
                                stopX).setDuration(duration).start();
    }

    @Override
    public void callbackMoveValue(int thisScrollX, int moveValue) {
        animatorMove(1000, thisScrollX, moveValue /*, DIRECTION.RIGHT*/);
    }

    @Override
    public void callbackMoveLeft(int thisScrollX) {
        animatorMove(1000, thisScrollX, -0f /*, DIRECTION.LEFT*/);
    }

    @Override
    public void callbackMoveRight(int thisScrollX) {
        animatorMove(1000,
                thisScrollX,
                thisViewWidth - (blueRectangleView.getMeasuredWidth() + tabViewHeadGroup.title2ChlidView.getMeasuredWidth() / 2) /*, DIRECTION.RIGHT*/);
    }

    @Override
    public void callbacToLeftContent() {
        callbackMoveLeft(thisViewWidth - (blueRectangleView.getMeasuredWidth() + tabViewHeadGroup.title2ChlidView.getMeasuredWidth() / 2));
    }

    @Override
    public void callbackToRightContent() {
        callbackMoveRight(tabViewContentGroup.getScrollX());
    }

    private enum DIRECTION {
        LEFT,
        RIGHT
    }
}

 

裏面一層的ViewGroup,用於管理標題文字與紅色滑動條,稱爲頭部spa

TabViewHeadGroup3d

package custom.view.upgrade.my_tab_host;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import custom.view.R;

public class TabViewHeadGroup extends ViewGroup implements View.OnClickListener {

    private final String TAG = TabViewHeadGroup.class.getSimpleName();

    public static final int LEFT_RIGHT = 60;

    private String title1 = "首頁一";
    private String title2 = "首頁二";

    /**
     * 設置標題一
     */
    public void setTitle1(String title1) {
        this.title1 = title1;
    }

    /**
     * 設置標題二
     */
    public void setTitle2(String title2) {
        this.title2 = title2;
    }

    public TabViewHeadGroup(Context context, AttributeSet attrs) {
        super(context, attrs);

        // setBackgroundColor(getResources().getColor(R.color.colorAccent));
        setBackgroundColor(Color.YELLOW);
    }

    private int thisViewWidth;
    private int thisViewHeight;

    private View slidingChildView;

    private View title1ChildView;
    public View title2ChlidView;

    private int modeW;
    private int modeH;

    /**
     * Xml指定類加載完成後,就會調用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        slidingChildView = getChildAt(0);
        title1ChildView = getChildAt(1);
        title2ChlidView = getChildAt(2);

        setListenter();

        /*TextView viewTitle1 = findViewById(R.id.tv_title1);
        Log.d(TAG, "onFinishInflate() ---->" + viewTitle1);
        viewTitle1.setText("111111000");*/
    }

    /**
     * 設置兩個標題的點擊事件
     */
    private void setListenter() {
        title1ChildView.setOnClickListener(this);
        title2ChlidView.setOnClickListener(this);
    }

    private ICallbackContent iCallbackContent;

    /**
     * 設置監聽,回調到--->TabViewContent
     */
    public void setCallbackContent(ICallbackContent iCallbackContent) {
        this.iCallbackContent = iCallbackContent;
    }

    private ICallbackContent iCallbackContent2;

    /**
     * 設置監聽,回調到--->TabViewContent
     */
    public void setiCallbackContent2(ICallbackContent iCallbackContent) {
        this.iCallbackContent2 = iCallbackContent;
    }

    /**
     * 測量本身的孩子
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 測量子控件,獲得父控件對當前控件測量後的寬高值
        thisViewWidth = MeasureSpec.getSize(widthMeasureSpec);
        thisViewHeight = MeasureSpec.getSize(heightMeasureSpec);

        modeW = MeasureSpec.getMode(widthMeasureSpec);
        modeH = MeasureSpec.getMode(heightMeasureSpec);

        Log.d(TAG, "thisViewWidth:" + thisViewWidth + " thisViewHeiht:" + thisViewHeight + " modeW:" + modeW + " modeH:" + modeH);


        Log.d(TAG, "測量前 slidingChildView.getLayoutParams().width:" + slidingChildView.getLayoutParams().width
                + " slidingChildView.getLayoutParams().height:" + slidingChildView.getLayoutParams().height);
        // 測量前 slidingChildView.getLayoutParams().width:300 slidingChildView.getLayoutParams().height:60
        // 測量前 slidingChildView.getLayoutParams().width:-2 slidingChildView.getLayoutParams().height:-2

        // 給子控件View測量,子控件設置了多少px,就測量多少px
        slidingChildView.measure(slidingChildView.getLayoutParams().width, slidingChildView.getLayoutParams().height);
        // slidingChildView.measure(0, 0); // 設置爲0,讓系統去爲我測量

        /*Log.d(TAG, "測量後 slidingChildView.getMeasuredWidth():" + slidingChildView.getMeasuredWidth()
                + " slidingChildView.getMeasuredHeight():" + slidingChildView.getMeasuredHeight());*/
        // 測量後 slidingChildView.getMeasuredWidth():145 slidingChildView.getMeasuredHeight():20
        // 測量後 slidingChildView.getMeasuredWidth():145 slidingChildView.getMeasuredHeight():20

        // 其實這一步是能夠不用作的,父控件會去給子控件測量
        // setMeasuredDimension(thisViewWidth, thisViewHeight);

        // 測量兩個標題的寬和高
        title1ChildView.measure(title1ChildView.getLayoutParams().width, title1ChildView.getLayoutParams().height);
        title2ChlidView.measure(title2ChlidView.getLayoutParams().width, title2ChlidView.getLayoutParams().height);

        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);

        // 注意:getMeasuredWidth() 是獲得當前本身測量後的寬度
        Log.d(TAG, "測量方法 setMeasuredDimension getMeasuredWidth():" + getMeasuredWidth() + " getMeasuredHeight():" + getMeasuredHeight());

        if (MeasureSpec.EXACTLY == modeW) {
            // widthMeasureSpec = parentViewGroup.getMeasuredWidth(); // 若是是沒法肯定的值,-1 match_parent,就賦值父控件測量後的寬度
            Log.d(TAG, "MeasureSpec.EXACTLY");
        } else if (MeasureSpec.AT_MOST == modeW) {
            Log.d(TAG, "MeasureSpec.AT_MOST");
        } else if (MeasureSpec.UNSPECIFIED == modeW) {
            Log.d(TAG, "MeasureSpec.UNSPECIFIED");
        }

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.d(TAG, "測量後 slidingChildView.getMeasuredWidth():" + slidingChildView.getMeasuredWidth()
                + " slidingChildView.getMeasuredHeight():" + slidingChildView.getMeasuredHeight());
        // 測量後 slidingChildView.getMeasuredWidth():145 slidingChildView.getMeasuredHeight():20

        // 給子控件排版
        slidingChildView.layout(LEFT_RIGHT,
                thisViewHeight - slidingChildView.getMeasuredHeight(),
                slidingChildView.getMeasuredWidth() + LEFT_RIGHT,
                thisViewHeight);

        // 加上底部滑動條的一半/2 在減傷自身的一半 就居中了
        int value1 = (slidingChildView.getMeasuredWidth() / 2) - title1ChildView.getMeasuredWidth() / 2  ;

        // 測量後的兩個標題值打印
        Log.d(TAG, "title1ChildView.getMeasuredWidth():" + title1ChildView.getMeasuredWidth() + " title1ChildView.getMeasuredHeight():" + title1ChildView.getMeasuredHeight());
        Log.d(TAG, "title2ChlidView.getMeasuredWidth():" + title2ChlidView.getMeasuredWidth() + " title2ChlidView.getMeasuredHeight():" + title2ChlidView.getMeasuredHeight());

        // 給兩個標題排版,固定位置先
        title1ChildView.layout(LEFT_RIGHT + value1 ,
                (thisViewHeight / 2) - (title1ChildView.getMeasuredHeight() / 2),
                title1ChildView.getMeasuredWidth() + LEFT_RIGHT + value1,
                (thisViewHeight / 2) - (title1ChildView.getMeasuredHeight() / 2) + title1ChildView.getMeasuredHeight());

        title2ChlidView.layout((thisViewWidth - LEFT_RIGHT) - title2ChlidView.getMeasuredWidth(),
                (thisViewHeight / 2) - (title2ChlidView.getMeasuredHeight() / 2),
                ((thisViewWidth - LEFT_RIGHT) - title2ChlidView.getMeasuredWidth()) + title2ChlidView.getMeasuredWidth(),
                (thisViewHeight / 2) - (title2ChlidView.getMeasuredHeight() / 2) + title2ChlidView.getMeasuredHeight());

        int parentHeight = 0;
        int parentWidth = 0;

        // 獲得父控件的高度,也就是屏幕的高度
        ViewGroup viewParentGroup = (ViewGroup) getParent();
        if (null != viewParentGroup) {
            parentHeight = viewParentGroup.getMeasuredHeight();
            parentWidth = viewParentGroup.getMeasuredWidth();
            Log.d(TAG, " parentHeight:" + parentHeight + " parentWidth:" + parentWidth);
        }

        // Log.d(TAG,  "l:" + l + " t:" + t + " b:" + b + " r:" + r); // 獲得當前TabViewHead距離左右上下邊值

        // Log.d(TAG, "getMeasuredHeight():" + getMeasuredHeight()); // 獲得當前TabViewHead測量後的高度 131
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_title1:
                // Toast.makeText(getContext(), "title1-", Toast.LENGTH_SHORT).show();
                /*slidingChildView.setPadding(LEFT_RIGHT + 200,
                        thisViewHeight - slidingChildView.getMeasuredHeight(),
                        slidingChildView.getMeasuredWidth() + LEFT_RIGHT + 200,
                        thisViewHeight);*/

                // tabViewContentGroup.moveToLeft();
                if (iCallbackContent != null) {
                    iCallbackContent.callbacToLeftContent();
                }
                if (iCallbackContent2 != null) {
                    iCallbackContent2.callbacToLeftContent();
                }
                animatorLeft();
                break;
            case R.id.tv_title2:
                // tabViewContentGroup.moveToRight();
                if (null != iCallbackContent) {
                    iCallbackContent.callbackToRightContent();
                }
                if (iCallbackContent2 != null) {
                    iCallbackContent2.callbackToRightContent();
                }
                // Toast.makeText(getContext(), "title2", Toast.LENGTH_SHORT).show();
                animatorRight();
                break;
            default:
                break;
        }
    }

    // 判斷是不是右邊
    private boolean isRight;

    private void animatorLeft() {
        ObjectAnimator.ofFloat(slidingChildView,
                "translationX",
                thisViewWidth - (slidingChildView.getMeasuredWidth() + title2ChlidView.getMeasuredWidth() / 2),
                0f).setDuration(1000).start(); // 設置X軸移動
        isRight = false;
    }

    private void animatorRight() {
        ObjectAnimator.ofFloat(slidingChildView,
                "translationX",
                0f, thisViewWidth - (slidingChildView.getMeasuredWidth() + title2ChlidView.getMeasuredWidth() / 2)).setDuration(1000).start(); // 設置X軸移動
        isRight = true;
    }

    public ICallbackHead implementHead() {
        return new ICallbackHead() {

            @Override
            public void callbackToLeftHead() {
                if (isRight) {
                    animatorLeft();
                }
            }

            @Override
            public void callbackToRightHead() {
                if (!isRight) {
                    animatorRight();
                }
            }
        };
    }
}

 

裏面一層的ViewGroup,用於管理第一個頁面/第二個頁面,稱爲內容code

TabViewContentGroup:

 

package custom.view.upgrade.my_tab_host;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.widget.Toast;

import custom.view.R;

public class TabViewContentGroup extends ViewGroup {

    private static final String TAG = TabViewContentGroup.class.getSimpleName();

    // 定義手勢識別器,更精準
    private GestureDetector gestureDetector;

    // 定義滑動器
    private Scroller scroller;

    // 用於X值累加
    private int distanceXSum;

    /**
     * 此構造方法是專門給其餘類使用的,例如:TabViewHeadGroup使用
     * @param context
     */
    public TabViewContentGroup(Context context){
        super(context);

        initView(context);
    }

    /**
     * 此構造方法是專門給佈局Xml使用的
     * @param context
     * @param attrs
     */
    public TabViewContentGroup(Context context, AttributeSet attrs) {
        super(context, attrs);

        initView(context);
    }

    private void initView(Context context) {

        gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener(){

            /**
             * 滑動監聽方法
             * @param e1 手指按下的事件
             * @param e2 手指在操做時候的事件
             * @param distanceX 當前X軸誤差值
             * @param distanceY 當前Y軸誤差值
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                // 滑動越界處理
                Log.d(TAG, "手勢識別器onScroll --> distanceX:" + distanceX + " getScrollX():" + getScrollX());
                // 第一種方式滑動,因爲scrollBy是累加的,因此直接傳入distanceX就可讓屏幕動起來了
                // scrollBy((int) distanceX, getScrollY());

                // 第二種方式滑動,因爲scrollTo不是累加的,因此須要定義一個變量來記錄累加
                distanceXSum += distanceX;
                if (distanceXSum < 0) {
                    distanceXSum = 0;
                } else if (distanceXSum > w) {
                    distanceXSum = w;
                }
                Log.d(TAG, "--------------distanceXSum:" + distanceXSum + " getScrollX:" + getScrollX());
                if (null != iCallbackBlueRectangle) {
                    iCallbackBlueRectangle.callbackMoveValue(getScrollX(), distanceXSum);
                }
                scrollTo(distanceXSum, getScrollY());
                return true; // 代碼滑動方法處理了
            }

            /**
             * 用雙擊去測試
             */
            /*@Override
            public boolean onDoubleTap(MotionEvent e) {
                super.onDoubleTap(e);
                // scrollTo(-w, getScrollY());

                // 採用緩慢滑動
                int dx = 0 - getScrollX();
                // dx 規律是,整數往<---移動   從整數 到 負數,因此就移動到最左邊了
                // dx 規律是,負數往--->移動   從負數 到 整數,因此就移動到最右邊了
                // 0 - 66 = -66
                // 88 - 66 = 22
                // scroller.startScroll(-20, getScrollY(), -90, getScrollY(), 1000);

                invalidate();
                scroller.startScroll(getScrollX(), getScrollY(), -getMeasuredWidth(), getScrollY(), 1000);

                Toast.makeText(getContext(), "你雙擊了 dx:" + dx + " w:" + w, Toast.LENGTH_LONG).show();

                return true;
            }*/

        });

        scroller = new Scroller(context);

        /*setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "你點擊了", Toast.LENGTH_LONG).show();
                scrollTo(260, getScrollY());
                moveToRight();
            }
        });*/
    }


    // 定義兩個佈局頁面子控件
    private View layoutChildView1;
    private View layoutChildView2;

    /**
     * 當Xml文件指定加載成爲了View對象後,會調用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        // 獲得兩個子控件
        layoutChildView1 = getChildAt(0);
        layoutChildView2 = getChildAt(1);
    }

    private int w;
    private int h;

    private int modeW;
    private int modeH;

    /**
     * 測量子控件的寬高
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);

        modeW = MeasureSpec.getMode(widthMeasureSpec);
        modeH = MeasureSpec.getMode(heightMeasureSpec);

        setMeasuredDimension(w, h);

        /*layoutChildView1.measure(layoutChildView1.getLayoutParams().width, getMeasuredHeight());
        layoutChildView2.measure(layoutChildView2.getLayoutParams().width, getMeasuredHeight());*/

        Log.d(TAG, "測量方法 getMeasuredWidth():" + getMeasuredWidth() + " getMeasuredHeight():" + getMeasuredHeight());

        // 對於這種子控件LinearLayout,傳入0系統會自動測量,就像ListView同樣
        layoutChildView1.measure(getMeasuredWidth(), getMeasuredHeight());
        layoutChildView2.measure(getMeasuredWidth(), getMeasuredHeight());

        // 這裏爲何是 -1 ?,是由於在父控件TabViewHead "tabViewContentGroup.measure(tabViewContentGroup.getLayoutParams().width==-1"
        // 爲毛w=1073741823這種值?,是由於View--getSize方法負數就返回(measureSpec & ~MODE_MASK);
        Log.d(TAG, "測量以前的值打印:" + widthMeasureSpec + " 轉換爲Size的值 w:" + w + " 轉換的modeW:" + modeW);

        ViewGroup parentViewGroup = (ViewGroup) getParent();

        if (MeasureSpec.EXACTLY == modeW) {
            // widthMeasureSpec = parentViewGroup.getMeasuredWidth(); // 若是是沒法肯定的值,-1 match_parent,就賦值父控件測量後的寬度
            Log.d(TAG, "精確模式");
        } else if (MeasureSpec.AT_MOST == modeW) {
            Log.d(TAG, "自適應模式");
        }

        // 注意:getMeasuredWidth() 是獲得當前本身測量後的寬度
        Log.d(TAG, "測量方法 setMeasuredDimension getMeasuredWidth():" + getMeasuredWidth() + " getMeasuredHeight():" + getMeasuredHeight());
    }

    /**
     * 給子佈局排版,在ViewGroup中只能給子佈局排版,本身的排版交給父控件
     * @param changed 當發生改變
     * @param l 左邊線距離左邊的距離
     * @param t 上邊線距離頂邊的距離
     * @param r 右邊線距離左邊的距離
     * @param b 底邊線距離頂邊的距離
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 這個是沒法獲取到子控件測量後的高和寬,由於這個子控件是屬於VIewGroup,除非子控件屬於View-->setMeasuredDimension()
        Log.d(TAG, "指定方法 layoutChildView1.getMeasuredWidth():" + layoutChildView1.getMeasuredWidth());
        Log.d(TAG, "指定方法 layoutChildView1.getMeasuredHeight():" + layoutChildView1.getMeasuredHeight());

        // 這個獲得的是-1,由於是match_parent,若是是xxxdp xxxpx 就能夠獲取到了
        Log.d(TAG, "指定方法.getLayoutParams().height:" + layoutChildView1.getLayoutParams().height);

        // 指定寬和高用當前TabViewGroup的寬和高
        // layoutChildView1.layout(0, 0, r, b);

        // 指定寬和高用當前TabViewGroup的寬和高
        // layoutChildView2.layout(r, 0, r * 2, b);

        // 也能夠經過TabViewGroup測量後的高和寬來指定位置
        /*layoutChildView1.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
        layoutChildView2.layout(getMeasuredWidth(), 0 , getMeasuredWidth() * 2, getMeasuredHeight());*/

        // 也可使用第三種方式,獲得父控件給子控件(TabViewGroup)測量後的高寬
        layoutChildView1.layout(0, 0, w, h);
        layoutChildView2.layout(w, 0, w * 2, h);
    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);

        Log.d(TAG, ">>>>>>>>>>>>>>> 內容體的觸摸方法執行了....");

        gestureDetector.onTouchEvent(event);

        if (event.getAction() == MotionEvent.ACTION_UP) {
            onUpEvent(event);
        }

        return true;
    }

    private int actionX;

    /**
     * 處理UP事件的方法
     */
    private void onUpEvent(MotionEvent event) {
        if (getScrollX() < (w / 2)) {
            actionX = 0;
            Log.d(TAG, "Up to left...");
            if (null != iCallbackHead) {
                iCallbackHead.callbackToLeftHead();
            }
            if (null != iCallbackBlueRectangle) {
                iCallbackBlueRectangle.callbackMoveLeft(getScrollX());
            }
        } else if (getScrollX() > (w / 2)) {
            actionX = w;
            Log.d(TAG, "Up to right...");
            if (null != iCallbackHead) {
                iCallbackHead.callbackToRightHead();
            }
            if (null != iCallbackBlueRectangle) {
                iCallbackBlueRectangle.callbackMoveRight(getScrollX());
            }
        }

        // 這種方式滑動,看起來沒有動畫
        // scrollTo(actionX, getScrollY());

        // 採用緩慢滑動
        int dx = actionX - getScrollX();
        // dx 規律是,整數往<---移動   從整數 到 負數,因此就移動到最左邊了
        // dx 規律是,負數往--->移動   從負數 到 整數,因此就移動到最右邊了
        // 0 - 66 = -66
        // 88 - 66 = 22
        // scroller.startScroll(-20, getScrollY(), -90, getScrollY(), 1000);
        scroller.startScroll(getScrollX(), getScrollY(), dx, getScrollY(), 1000);
        invalidate();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }

    /**
     * 實現化ICallbackContent接口
     * @return
     */
    public ICallbackContent implementContent() {
        return new ICallbackContent() {
            @Override
            public void callbacToLeftContent() {
                moveToLeft();
            }

            @Override
            public void callbackToRightContent() {
                moveToRight();
            }
        };
    }

    /**
     * 內容頁面移動到最右邊的頁面
     */
    private void moveToRight() {
        // Toast.makeText(getContext()," moveToRight", Toast.LENGTH_LONG).show();
        // scrollTo(w, getScrollY());

        //去補值,保證左右滑動效果
        distanceXSum = w;

        postInvalidate();
        scroller.startScroll(0, getScrollY(), getMeasuredWidth(), getScrollY(),1200);
    }

    /**
     * 內容頁面移動到最左邊的頁面
     */
    private void moveToLeft() {
        // Toast.makeText(getContext()," moveToLeft", Toast.LENGTH_LONG).show();
        // scrollTo(0, getScrollY());

        //去補值,保證左右滑動效果
        distanceXSum = 0;

        postInvalidate();
        scroller.startScroll(getMeasuredWidth(), getScrollY(), -getMeasuredWidth(), getScrollY(),1200);
    }

    private ICallbackHead iCallbackHead;

    /**
     * 設置接口回調到Head
     */
    public void setCallbackHead(ICallbackHead iCallbackHead) {
        this.iCallbackHead = iCallbackHead;
    }

    private ICallbackBlueRectangle iCallbackBlueRectangle;

    /**
     * 設置接口回到到主控制器去滑動
     */
    public void setCallbackBlueRectangle(ICallbackBlueRectangle iCallbackBlueRectangle) {
        this.iCallbackBlueRectangle = iCallbackBlueRectangle;
    }

    /**
     * 獲取當前Content的X距離值
     * @return
     */
    public int getScrollXValue() {
        return getScrollX();
    }

}
相關文章
相關標籤/搜索