Android-自定义TabHost
Posted 原创Android 努力学习 专一安卓 持之以恒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-自定义TabHost相关的知识,希望对你有一定的参考价值。
效果图:
布局代码相关:
<!-- 自定义简单的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 :
<?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,需要管理好三个子控件:
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,用于管理标题文字与红色滑动条,称为头部
TabViewHeadGroup
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()); //Android 片段,在 TabHost 中找不到 id 的视图如何在android中为Fragment TabHost提供底部阴影?