效果圖: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(); } }