效果图:
布局代码相关:
<!-- 自定义简单的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();
}
}