zoukankan      html  css  js  c++  java
  • 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()." + slidingChildView.getLayoutParams().width
                    + " slidingChildView.getLayoutParams().height:" + slidingChildView.getLayoutParams().height);
            // 测量前 slidingChildView.getLayoutParams().300 slidingChildView.getLayoutParams().height:60
            // 测量前 slidingChildView.getLayoutParams().-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();
        }
    
    }


  • 相关阅读:
    【经典数据结构】B树与B+树
    【经典算法】线性时间排序
    【经典算法】归并排序
    【经典算法】快速排序
    python模块之shelve
    python模块之pickle
    python模块之json
    python之序列化
    python模块之shutil和zipfile
    python模块之sys
  • 原文地址:https://www.cnblogs.com/android-deli/p/9900643.html
Copyright © 2011-2022 走看看