首先声明一点,写这两个View纯粹为了练习而已,要实现这个功能完全可以直接用ViewPager和TabLayout完成
下面首先给出效果图

Activity、Adapter代码如下
public class MyViewPagerActivity extends Activity {
private MyViewPager mVp;
private MyTabLayout mTab;
public static final String[] mTitles = {"视频","图片","小说","杂志","明星","体育","游戏","竞技","中医"};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewpager);
mVp = findViewById(R.id.vp);
mTab = findViewById(R.id.tab);
BaseAdapter adapter = new MyBaseAdapter();
mVp.setAdapter(adapter);
mTab.setViewPager(mVp);
}
class MyBaseAdapter extends BaseAdapter {
@Override
public int getItemCount() {
return mTitles.length;
}
@Override
public View getView(int position) {
return MyViewManager.getInstance().getView(getApplicationContext(), position);
}
@Override
public String getTitle(int position) {
return mTitles[position];
}
}
static class MyViewManager {
private SparseArray<View> mViews = new SparseArray<>();
public static MyViewManager getInstance() {
return MyViewManagerHolder.mViewManager;
}
public View getView(Context context, int position) {
if (mViews.get(position) != null) {
return mViews.get(position);
}
RecyclerView rv = new RecyclerView(context);
rv.setLayoutManager(new LinearLayoutManager(context));
rv.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
rv.setAdapter(new CommonAdapter(context, mTitles[position]));
mViews.put(position, rv);
return rv;
}
private static class MyViewManagerHolder {
private static MyViewManager mViewManager = new MyViewManager();
}
}
}
public class CommonAdapter extends RecyclerView.Adapter<CommonAdapter.ViewHolder> {
private List<String> mList;
private OnItemClickListener mListener;
private Context mContext;
CommonAdapter(Context context, String title) {
mList = new ArrayList<>();
mContext = context;
for (int i=0; i<100; i++) {
mList.add(title + " " + i);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main_layout, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.mTv.setText(mList.get(position));
holder.mTv.setOnClickListener((v) -> {
Toast.makeText(mContext, mList.get(position), Toast.LENGTH_SHORT).show();
});
holder.itemView.setOnClickListener((view) -> {
if (mListener != null) {
mListener.onItemClick(view, holder.getLayoutPosition());
}
});
holder.itemView.setOnLongClickListener((view) -> {
if (mListener != null) {
mListener.onItemLongClick(view, holder.getLayoutPosition());
}
return true;
});
}
@Override
public int getItemCount() {
return mList.size();
}
public void removeData(int position) {
mList.remove(position);
notifyItemRemoved(position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
class ViewHolder extends RecyclerView.ViewHolder {
private final TextView mTv;
private ViewHolder(@NonNull View itemView) {
super(itemView);
mTv = itemView.findViewById(R.id.tv);
}
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
}
最后给出两个自定义View的代码
public class MyViewPager extends ViewGroup {
private int mPerItemWidth;
private Scroller mScroller;
private BaseAdapter mAdapter;
private OnPageChangeListener mPageChangeListener;
private int mCurrentPosition;
private int mTouchDownX;
private int mTouchDownY;
private VelocityTracker mTracker;
public MyViewPager(Context context) {
this(context, null);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
mTracker = VelocityTracker.obtain();
}
public void setCurrentItem(int index) {
int endTranslationX = -index * mPerItemWidth;
mScroller.startScroll((int)getTranslationX(), 0, (int)(endTranslationX - getTranslationX()), 0, 1000);
mCurrentPosition = index;
invalidate();
}
public void setAdapter(BaseAdapter adapter) {
if (adapter != null && adapter.getItemCount() > 0) {
removeAllViews();
for(int i=0; i<adapter.getItemCount(); i++) {
addView(adapter.getView(i), new ViewGroup.LayoutParams(-1, -1));
}
mAdapter = adapter;
}
}
public void setOnPageChangeListener(OnPageChangeListener listener) {
mPageChangeListener = listener;
}
public BaseAdapter getAdapter() {
return mAdapter;
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
setTranslationX(mScroller.getCurrX());
invalidate();
if (mPageChangeListener != null) {
mPageChangeListener.onTranslationXChange(getTranslationX() / mPerItemWidth);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (hasChildView()) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec) * mAdapter.getItemCount(),
MeasureSpec.getSize(heightMeasureSpec));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (hasChildView()) {
mPerItemWidth = getMeasuredWidth() / getChildCount();
for (int i=0; i<getChildCount(); i++) {
View childView = getChildAt(i);
childView.layout( mPerItemWidth * i, 0,
mPerItemWidth * (i + 1), getMeasuredHeight());
}
}
Log.d("onLayoutParent", " left: " + l + " top: " + t + " right: " + r + " bottom: " + b);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int currentX = (int) event.getX();
int currentY = (int) event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTracker.addMovement(event);
mTouchDownX = currentX;
mTouchDownY = currentY;
break;
case MotionEvent.ACTION_MOVE:
mTracker.addMovement(event);
int dx = currentX - mTouchDownX;
int dy = currentY - mTouchDownY;
if (Math.sqrt(dx * dx + dy * dy) > ViewConfiguration.get(getContext()).getScaledTouchSlop() &&
Math.abs(dx) > Math.abs(dy)) {
return true;
}
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
int currentX = (int) event.getX();
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
int dx = currentX - mTouchDownX;
setTranslationX(getTranslationX() + dx);
if (getTranslationX() > 0) {
setTranslationX(0);
mTouchDownX = currentX;
} else if (getTranslationX() < -mPerItemWidth * (getChildCount() - 1)) {
setTranslationX(-mPerItemWidth * (getChildCount() - 1));
mTouchDownX = currentX;
}
if (mPageChangeListener != null) {
mPageChangeListener.onTranslationXChange(getTranslationX() / mPerItemWidth);
}
break;
case MotionEvent.ACTION_UP:
int lastPosition = mCurrentPosition;
if (-getTranslationX() / mPerItemWidth % 1 > 0.5) {
if (-mCurrentPosition * mPerItemWidth > getTranslationX()) {
mCurrentPosition++;
}
} else {
if (-mCurrentPosition * mPerItemWidth < getTranslationX()) {
mCurrentPosition--;
}
}
if (lastPosition == mCurrentPosition) {
mTracker.computeCurrentVelocity(1000);
float xVelocity = mTracker.getXVelocity();
if (Math.abs(xVelocity) > 3000) {
if (xVelocity > 0) {
if (mCurrentPosition > 0) {
mCurrentPosition--;
}
} else {
if (mCurrentPosition < mAdapter.getItemCount() - 1) {
mCurrentPosition++;
}
}
}
}
setCurrentItem(mCurrentPosition);
if (mPageChangeListener != null) {
mPageChangeListener.onPageChange(lastPosition, mCurrentPosition);
}
break;
}
return true;
}
private boolean hasChildView() {
return mAdapter != null && mAdapter.getItemCount() > 0;
}
public interface OnPageChangeListener {
void onPageChange(int oldPosition, int newPosition);
void onTranslationXChange(float percent);
}
}
public class MyTabLayout extends ViewGroup {
private int mPerTabWidth = 150;
private int mDividerHeight = 10;
private int mDividerColor = Color.BLUE;
private int mVisibleWidth;
private MyViewPager mVp;
private int mDownX;
private int minTranslationX;
private int mNormalTextColor = Color.BLACK;
private int mSelectedTextColor = Color.BLUE;
private int mSelectedPosition;
private int mDownY;
private static final String TAG = "MyTabLayout";
@RequiresApi(api = Build.VERSION_CODES.M)
public MyTabLayout(Context context) {
this(context, null);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public MyTabLayout(Context context, AttributeSet set) {
super(context, set);
setForeground(new ColorDrawable(Color.parseColor("#22000000")));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int childHeightMode = heightMode;
if (heightMode != MeasureSpec.EXACTLY) {
heightSize = (int) (getContext().getResources().getDisplayMetrics().density * 44);
childHeightMode = MeasureSpec.EXACTLY;
}
mVisibleWidth = widthSize;
measureChildren(MeasureSpec.makeMeasureSpec(mPerTabWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, childHeightMode));
setMeasuredDimension((getChildCount() - 1) * mPerTabWidth, heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i=0; i<getChildCount() - 1; i++) {
getChildAt(i).layout(mPerTabWidth * i, 0, mPerTabWidth * (i + 1), b);
}
getChildAt(getChildCount() - 1).layout(0, b - mDividerHeight / 2, mPerTabWidth, b);
minTranslationX = -getWidth() + mVisibleWidth;
}
public void setViewPager(MyViewPager viewPager) {
if (viewPager != null) {
mVp = viewPager;
for (int i=0; i<mVp.getChildCount(); i++) {
addTab(new Tab(mVp.getAdapter().getTitle(i)));
}
mVp.setOnPageChangeListener(new MyViewPager.OnPageChangeListener() {
@Override
public void onPageChange(int oldPosition, int newPosition) {
changeTextColor(oldPosition, newPosition);
mSelectedPosition = newPosition;
}
@Override
public void onTranslationXChange(float percent) {
int middleItemPosition = mVisibleWidth / mPerTabWidth / 2;
if (-percent < middleItemPosition) {
Log.d(TAG, "onTranslationXChange: X" + percent);
} else if (-percent > Tab.count - middleItemPosition) {
Log.d(TAG, "onTranslationXChange: Z" + percent);
} else {
Log.d(TAG, "onTranslationXChange: Y");
setTranslationX((percent + middleItemPosition) * mPerTabWidth);
}
if (getTranslationX() < minTranslationX) {
Log.d(TAG, "onTranslationXChange: " + getTranslationX() + " " + minTranslationX);
setTranslationX(minTranslationX);
}
getChildAt(getChildCount() - 1).setTranslationX(-mPerTabWidth * percent);
}
});
addView(new DividerView(getContext()));
}
}
public void addTab(Tab tab) {
addView(convertTabToView(tab));
}
private void setCurrentItem(int position) {
mVp.setCurrentItem(position);
changeTextColor(mSelectedPosition, position);
mSelectedPosition = position;
}
private void changeTextColor(int oldPosition, int newPosition) {
((TextView)getChildAt(oldPosition)).setTextColor(mNormalTextColor);
((TextView)getChildAt(newPosition)).setTextColor(mSelectedTextColor);
}
private View convertTabToView(Tab tab) {
TextView textView = new TextView(getContext());
textView.setTextColor(mSelectedPosition == tab.id ? mSelectedTextColor : mNormalTextColor);
textView.setText(tab.mTitleName);
textView.setGravity(Gravity.CENTER);
textView.setLayoutParams(new ViewGroup.LayoutParams(mPerTabWidth, -1));
textView.setOnClickListener((view) -> {
setCurrentItem(tab.id);
});
return textView;
}
public static class Tab {
public String mTitleName;
public int id = count++;
public static int count;
public Tab(String titleName) {
mTitleName = titleName;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int currentX = (int) event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
int dx = currentX - mDownX;
setTranslationX(getTranslationX() + dx);
if (getTranslationX() > 0) {
setTranslationX(0);
mDownX = currentX;
} else if (getTranslationX() < minTranslationX) {
setTranslationX(minTranslationX);
mDownX = currentX;
}
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int currentX = (int) event.getX();
int currentY = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = currentX;
mDownY = currentY;
return false;
case MotionEvent.ACTION_MOVE:
return Math.sqrt((currentX - mDownX) * (currentX - mDownX) + (currentY - mDownY) * (currentY - mDownY)) >=
ViewConfiguration.get(getContext()).getScaledTouchSlop();
case MotionEvent.ACTION_UP:
return false;
default:
return false;
}
}
class DividerView extends View {
private Paint mPaint;
public DividerView(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(mDividerHeight);
mPaint.setColor(mDividerColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mPerTabWidth, mDividerHeight);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawLine(0, 0, getWidth(), 0, mPaint);
}
}
}