效果
TabActivity
package com.coral3.ah.ui.activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;
import com.coral3.ah.R;
import com.coral3.ah.components.MoveTabBlock;
import com.coral3.ah.ui.fragment.tab.TabFragment1;
import com.coral3.ah.ui.fragment.tab.TabFragment2;
import com.coral3.ah.ui.fragment.tab.TabFragment3;
import com.coral3.common_module.utils.InitUtil;
import com.coral3.common_module.utils.LogUtil;
public class TabActivity extends AppCompatActivity implements View.OnClickListener{
private ViewPager viewPager;
private Fragment[] views;
private LinearLayout llTabLayout;
private MoveTabBlock moveTabBlock;
private int childCount;
private Scroller mScroller;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab);
initView();
initListener();
}
public void initView(){
viewPager = findViewById(R.id.vp_view_tabs);
views = new Fragment[3];
views[0] = new TabFragment1();
views[1] = new TabFragment2();
views[2] = new TabFragment3();
viewPager.setAdapter(new MyAdapter(getSupportFragmentManager(), 1));
llTabLayout = findViewById(R.id.ll_tabs_layout);
childCount = llTabLayout.getChildCount();
moveTabBlock = findViewById(R.id.tv_move_block);
mScroller = new Scroller(this);
chooseTab(0);
}
public void chooseTab(int pos){
for(int i = 0, len = childCount; i < len; i++){
LinearLayout childAt = (LinearLayout) llTabLayout.getChildAt(i);
((TextView) childAt.getChildAt(1)).setTextColor(InitUtil.getContext().getResources().getColor(R.color.text_no_select));
childAt.getChildAt(0).setEnabled(true);
}
LinearLayout childAt = (LinearLayout) llTabLayout.getChildAt(pos);
((TextView) childAt.getChildAt(1)).setTextColor(InitUtil.getContext().getResources().getColor(R.color.black));
childAt.getChildAt(0).setEnabled(false);
}
public void initListener(){
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
// 方式二
// ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) tvMoveBlock.getLayoutParams();
// lp.leftMargin = tvMoveBlock.getLeft() + 100;
// tvMoveBlock.setLayoutParams(lp);
// 方式一
// tvMoveBlock.offsetLeftAndRight(100);
// 方式三
// 获取点击视图的x/y轴坐标
LinearLayout childAt = (LinearLayout) llTabLayout.getChildAt(position);
int childAtWidth = childAt.getWidth(); // 获取tab块的宽度
int moveTabBlockWidth = moveTabBlock.getWidth(); // 滑动块的宽度
int[] location = new int[2];
childAt.getLocationOnScreen(location);
int x = location[0]; // 当前点击的块距离屏幕x坐标
int[] tabXY = new int[2];
moveTabBlock.getLocationOnScreen(tabXY);
int tabX = tabXY[0];// 当前移动块距离屏幕x坐标
LogUtil.d(String.valueOf(x) + "-" + tabXY[0]);
moveTabBlock.scrollTo(x - tabX + childAtWidth / 2 - moveTabBlockWidth / 2 + 13, 0);
chooseTab(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
for(int i = 0; i < childCount; i++){
llTabLayout.getChildAt(i).setTag(i);
llTabLayout.getChildAt(i).setOnClickListener(this);
}
}
@Override
public void onClick(View view) {
Log.d("yue-tag", view.getTag().toString());
viewPager.setCurrentItem((int)view.getTag());;
}
private class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(@NonNull FragmentManager fm, int behavior) {
super(fm, behavior);
}
@NonNull
@Override
public Fragment getItem(int position) {
return views[position];
}
@Override
public int getCount() {
return views.length;
}
}
}
MoveTabBlock
package com.coral3.ah.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
/**
* createBy 蓝之静云
*/
public class MoveTabBlock extends View {
// 是否允许滑动
private Boolean isEvent = false;
//定义两个变量用于存储按下view时所处的坐标
int lastX = 0;
int lastY = 0;
//滑动
Scroller scroller;
public MoveTabBlock(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(context);
}
@Override public boolean onTouchEvent(MotionEvent event) {
// 静止视图事件
if(!isEvent) return false;
//检测到触摸事件后 第一时间得到相对于父控件的触摸点坐标 并赋值给x,y
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
//触摸事件中绕不开的第一步,必然执行,将按下时的触摸点坐标赋值给 lastX 和 last Y
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
//触摸事件的第二步,这时候的x,y已经随着滑动操作产生了变化,用变化后的坐标减去首次触摸时的坐标得到 相对的偏移量
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
//触摸事件的第三步,必然执行,手指抬起时候触发,这里会将移动过的view还原到原来的位置,并且有过度效果不是突然移动
case MotionEvent.ACTION_UP:
//因为下面要使用父视图的引用来得到偏移量 所以要获得一个父视图引用
View viewGroup = (View) getParent();
//调用 startScroll 方法,参数为 起始X坐标,起始Y坐标,目的X坐标,目的Y坐标,过度动画持续时间
//这里使用了 viewGroup.getScrollX() 和 viewGroup.getScrollY() 作为起始坐标,ScrollY 和 ScrollX 记录了使用 scrollBy 进行偏移的量
//所以使用他们就等于是使用了现在的坐标作为起始坐标,目的坐标为他们的负数,就是偏移量为0的位置,也是view在没有移动之前的位置
scroller.startScroll(viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
-viewGroup.getScrollY(),
630);
//刷新view,这里很重要,如果不执行,下面的 computeScroll 方法就不会执行 computeScroll 方法是由 onDraw 方法调用的,而刷新 View 会调用 onDraw。
invalidate();
break;
}
return true;
}
/**
* 左右移动
* @param x x移动距离
* @param y y移动距离
*/
public void scrollTo(int x, int y){
View viewGroup = (View) getParent();
scroller.startScroll(viewGroup.getScrollX(),
viewGroup.getScrollY(),
-x,
-y,
800);
// 刷新视图
invalidate();
}
/**
* 是否对视图事件打开
* @param params
*/
public void openEvent(Boolean params){
this.isEvent = params;
}
public int[] getXY(){
int[] location = new int[2];
View viewGroup = (View) getParent();
location[0] = viewGroup.getScrollX();
location[1] = viewGroup.getScrollY();
return location;
}
@Override public void computeScroll() {
//在上面尝试刷新视图之后被调用,并且执行了 computeScrollOffset 方法,
//此方法根据上面传进来的起始坐标和目的坐标还有动画时间,进行计算每次移动的偏移量
//如果到达目的坐标 false ,如果不为零 说明没有到达目的坐标
if (scroller.computeScrollOffset()) {
//使用 scrollTo 方法进行移动,参数是从 scroller 的 getCurrX 以及 getCurrY 方法得到的,
// 这两个参数每次在执行 computeScrollOffset 之后都会改变,会越来越接近目的坐标。
((View) getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
// 再次刷新 view 也等于是在循环执行此方法 直到 computeScrollOffset 判断到达目的坐标为止,
// 循环次数和每次移动的坐标距离相关,每次移动的坐标距离又跟目的坐标的距离和动画时长有关
//通常距离越长,动画时间越长,循环次数越多
invalidate();
}
}
}
activity_tab.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="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"
android:background="#F0F0F0"
tools:context=".ui.activity.TabActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#fff"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true">
<LinearLayout
android:id="@+id/ll_tabs_layout"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:src="@drawable/tab_selector"
android:layout_width="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_height="wrap_content"/>
<TextView
android:text="推荐"
android:layout_marginLeft="3dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:src="@drawable/tab_selector"
android:layout_width="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_height="wrap_content"/>
<TextView
android:text="热榜"
android:layout_marginLeft="3dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:src="@drawable/tab_selector"
android:layout_width="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_height="wrap_content"/>
<TextView
android:text="高赞"
android:layout_marginLeft="3dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:gravity="center_vertical">
<com.coral3.ah.components.MoveTabBlock
android:id="@+id/tv_move_block"
android:background="@drawable/bg_radius_5"
android:layout_marginLeft="50dp"
android:layout_width="30dp"
android:layout_height="3dp"/>
<!-- <TextView-->
<!-- android:id="@+id/tv_move_block"-->
<!-- android:background="@drawable/bg_radius_5"-->
<!-- android:layout_marginLeft="53dp"-->
<!-- android:layout_width="30dp"-->
<!-- android:layout_height="3dp"/>-->
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/vp_view_tabs"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>
图片素材
tab_img1
tab_img2
tab_img3
tab_blue
tab_red
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="tabbar_no_act">#bfbfbf</color>
<color name="tabbar_act">#ffffff</color>
<color name="devide_line">#F0F0F0</color>
<color name="text_no_select">#515050</color>
</resources>
bg_radius_5.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="5dp"/>
<solid android:color="#03A9F4"/>
</shape>
tab_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:drawable="@drawable/tab_red"/>
<item android:state_enabled="true" android:drawable="@drawable/tab_blue"/>
</selector>
TabFragment1
package com.coral3.ah.ui.fragment.tab;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.coral3.ah.R;
/**
* A simple {@link Fragment} subclass.
* Use the {@link TabFragment1#newInstance} factory method to
* create an instance of this fragment.
*/
public class TabFragment1 extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
public TabFragment1() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment TabFragment1.
*/
// TODO: Rename and change types and number of parameters
public static TabFragment1 newInstance(String param1, String param2) {
TabFragment1 fragment = new TabFragment1();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_tab1, container, false);
}
}
tab_fragment2
package com.coral3.ah.ui.fragment.tab;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.coral3.ah.R;
/**
* A simple {@link Fragment} subclass.
* Use the {@link TabFragment2#newInstance} factory method to
* create an instance of this fragment.
*/
public class TabFragment2 extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
public TabFragment2() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment TabFragment2.
*/
// TODO: Rename and change types and number of parameters
public static TabFragment2 newInstance(String param1, String param2) {
TabFragment2 fragment = new TabFragment2();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_tab2, container, false);
}
}
tab_fragment3
package com.coral3.ah.ui.fragment.tab;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.coral3.ah.R;
/**
* A simple {@link Fragment} subclass.
* Use the {@link TabFragment3#newInstance} factory method to
* create an instance of this fragment.
*/
public class TabFragment3 extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
public TabFragment3() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment TabFragment3.
*/
// TODO: Rename and change types and number of parameters
public static TabFragment3 newInstance(String param1, String param2) {
TabFragment3 fragment = new TabFragment3();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_tab3, container, false);
}
}
fragment_tab1.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.tab.TabFragment1">
<ImageView
android:src="@drawable/tab_img1"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
fragment_tab2.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.tab.TabFragment2">
<ImageView
android:src="@drawable/tab_img2"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
fragment_tab3.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.tab.TabFragment3">
<ImageView
android:src="@drawable/tab_img3"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
*路漫长