效果图:

布局代码相关:

<!-- 自定义简单的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()); // 得到当前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,用于管理第一个页面/第二个页面,称为内容

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();
} }
												

Android-自定义TabHost的更多相关文章

  1. android自定义tabhost,tabcontent用intent获得

    地址:http://my.oschina.net/aowu/blog/36282 自己改的自定义tabhost组建,效果图如左.有更好的朋友可以相互交流一下,嘿嘿. 1.先上AndroidManife ...

  2. Android 自定义TabHost,TabWidget样式

    界面比较简单,要想做得漂亮换几张图片就可以了. 第一步:先在布局(这里用了main.xml创建时自动生成的)里面放上TabHost ,只要将TabHost控件托至屏幕中就可: <?xml ver ...

  3. Android底部TabHost API

    今天在项目中遇到了底部TabHost,顺便就写了一个底部TabHost的api继承即可使用非常简单,以下为源代码: 首先是自定义的TabHostActivity,如果要使用该TabHost继承该类即可 ...

  4. Android:TabHost实现Tab切换

    TabHost是整个Tab的容器,包含TabWidget和FrameLayout两个部分,TabWidget是每个Tab的表情,FrameLayout是Tab内容. 实现方式有两种: 1.继承TabA ...

  5. Android项目--tabhost

    所有牵扯到自定义布局的layout中尽量用RelativeLayout 在通讯录中如果像小米手机的UI那就是viewpager,在这里,我们做成静态的.通过tabhost来做. 1.布局 a) 直接用 ...

  6. Android: 自定义Tab样式,一种简单的方式。

    之前看到过论坛里已经有人发过自定义Tab样式的帖子,感觉有些复杂了,这里分享个简单的方法. 1.制作4个9patch的tab样式,可参考android默认的资源 tab_unselected.9.pn ...

  7. Android之TabHost实现Tab切换

    TabHost是整个Tab的容器,包含TabWidget和FrameLayout两个部分,TabWidget是每个Tab的表情,FrameLayout是Tab内容. 实现方式有两种: 1.继承TabA ...

  8. android中TabHost和RadioGroup

    android底部菜单应用 博客分类: android--UI示例 TabHostMenuRadioGroupButton  在android中实现菜单功能有多种方法. Options Menu:用户 ...

  9. Android中TabHost中实现标签的滚动以及一些TabHost开发的奇怪问题

    最近在使用TabHost的时候遇到了一些奇怪的问题,在这里总结分享备忘一下. 首先说一点TabActivity将会被FragmentActivity所替代,但是本文中却是使用的TabActivity. ...

  10. android 自定义动画

    android自定义动画注意是继承Animation,重写里面的initialize和applyTransformation,在initialize方法做一些初始化的工作,在applyTransfor ...

随机推荐

  1. Think in java.chm 第14章 多线程

    例子1引入线程概念通过得到当前线程方式循环主线程做某事 例子2演示了在主线程之外开启多个线程的基本方式 ( new一个extends Thread ) 例子3 ( task extends Threa ...

  2. 学习blus老师js(1)--基础

    1.网页换肤: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <t ...

  3. 转 linux 下装 usb driver

    http://www.george-smart.co.uk/wiki/Xilinx_JTAG_Linux

  4. SPM——How to use github

    In this semester, we take a class called 'Software Project Management'. And in this class, we have l ...

  5. Androdi Gradle build project info 很慢

    Androdi Gradle build project info 很慢 http://blog.csdn.net/stupid56862/article/details/78345584   原创  ...

  6. FireMoneky 画图 Point 赋值

    VCL 的 Canvas.Pen 对应FMX: Canvas.Stroke;VCL到 Canvas.Brush 对应FMX: Canvas.Fill. TCircle 圆形控件 Inkscape 0. ...

  7. curl返回302 found问题相关

    在使用curl 的时候 ,偶尔会遇到一些URL跳转到新的URL,即HTTP中的3XX(redirection,重定向 ) 此时如果不设置自动跳转倒新url的话,可能会出现问题. 一些网上的解决方法: ...

  8. Linux Makefile 教程(转)

    原文地址:http://blog.csdn.net/liang13664759/article/details/1771246 ------------------------------------ ...

  9. window.btoa()方法;使字符编码成base64的形式

    atob() 函数能够   解码  通过base-64编码的字符串数据.相反地, btoa() 函数能够从二进制数据“字符串” 创建 一个base-64编码的ASCII字符串.

  10. svg make a face

    1.创建项目 #使用simple模板 vue init webpack-simple vue-svg #安装依赖 cd vue-svg/ npm i #安装d3 npm i d3 --save 2.代 ...