当前位置: 首页 > news >正文

Android实战进阶 - CircleIndicator指示器样式定制

这好像是我两年前的一个需求效果,因需实现以下效果,所以直接修改了 CircleIndicator 框架中的部分代码,希望对你有所引导

该篇主要讲解的是卡片滑动时,底部指针提示效果

在这里插入图片描述

实现下方效果时要注意:默认状态、选中状态场景中背景颜色、长度、圆化

在这里插入图片描述

继续补

    • 基础了解
    • 项目实践
      • 基础、通用配置
        • 自定义属性
        • 自定义组件基类
        • 其他辅助类
      • CircleIndicator2 核心组件
      • 加工 核心组件
      • 使用方式

基础了解

如有兴趣了解 CircleIndicator 框架可直接去看源码,此篇仅做部分基础讲解,以实现上方需求为主

引入依赖

dependencies {implementation 'me.relex:circleindicator:2.1.6'
}

CircleIndicator 内部提供了三款自定义控件,分别作用于 ViewPager、 RecyclerView、ViewPager2

在这里插入图片描述

以下为 对应组件使用 CircleIndicator 的使用示例(组件提供了不同使用组件绑定 CircleIndicator 的方法)

  • ViewPager (CircleIndicator)
ViewPager viewpager = (ViewPager) view.findViewById(R.id.viewpager);
viewpager.setAdapter(adapter);CircleIndicator indicator = (CircleIndicator) view.findViewById(R.id.indicator);
indicator.setViewPager(viewpager);// optional
adapter.registerDataSetObserver(indicator.getDataSetObserver());
  • RecyclerView (CircleIndicator2)
RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);PagerSnapHelper pagerSnapHelper = new PagerSnapHelper();
pagerSnapHelper.attachToRecyclerView(recyclerView);CircleIndicator2 indicator = view.findViewById(R.id.indicator);
indicator.attachToRecyclerView(recyclerView, pagerSnapHelper);// optional
adapter.registerAdapterDataObserver(indicator.getAdapterDataObserver());
  • ViewPager2 (CircleIndicator3)
ViewPager2 viewpager = view.findViewById(R.id.viewpager);
viewpager.setAdapter(mAdapter);CircleIndicator3 indicator = view.findViewById(R.id.indicator);
indicator.setViewPager(viewpager);// optional
adapter.registerAdapterDataObserver(indicator.getAdapterDataObserver());

项目实践

我是从框架中直接剥离我所需要的组件,减少三方组件侵入,故我有以下考量

  • 因为我的场景是采用的 RecyclerView ,那么也就意味着用到了 CircleIndicator2 控件
  • 不论是框架中的哪个自定义组件都是基于 BaseCircleIndicator 做的二次扩展
  • 关于 BaseCircleIndicator、CircleIndicator2 组件用到的一些配置信息需要逐步剥离

基础、通用配置

如果不需要剥离单组件使用,可以结合框架,减少部分通用配置,只定制新的 CircleIndicator2 即可!

自定义属性
    <declare-styleable name="BaseCircleIndicator"><attr format="dimension" name="ci_width"/><attr format="dimension" name="ci_height"/><attr format="dimension" name="ci_margin"/><attr format="reference" name="ci_animator"/><attr format="reference" name="ci_animator_reverse"/><attr format="reference" name="ci_drawable"/><attr format="reference" name="ci_drawable_unselected"/><attr format="enum" name="ci_orientation"><!-- Defines an horizontal widget. --><enum name="horizontal" value="0"/><!-- Defines a vertical widget. --><enum name="vertical" value="1"/></attr><attr name="ci_gravity"><!-- Push object to the top of its container, not changing its size. --><flag name="top" value="0x30"/><!-- Push object to the bottom of its container, not changing its size. --><flag name="bottom" value="0x50"/><!-- Push object to the left of its container, not changing its size. --><flag name="left" value="0x03"/><!-- Push object to the right of its container, not changing its size. --><flag name="right" value="0x05"/><!-- Place object in the vertical center of its container, not changing its size. --><flag name="center_vertical" value="0x10"/><!-- Grow the vertical size of the object if needed so it completely fills its container. --><flag name="fill_vertical" value="0x70"/><!-- Place object in the horizontal center of its container, not changing its size. --><flag name="center_horizontal" value="0x01"/><!-- Grow the horizontal size of the object if needed so it completely fills its container. --><flag name="fill_horizontal" value="0x07"/><!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. --><flag name="center" value="0x11"/><!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. --><flag name="fill" value="0x77"/><!-- Additional option that can be set to have the top and/or bottom edges ofthe child clipped to its container's bounds.The clip will be based on the vertical gravity: a top gravity will clip the bottomedge, a bottom gravity will clip the top edge, and neither will clip both edges. --><flag name="clip_vertical" value="0x80"/><!-- Additional option that can be set to have the left and/or right edges ofthe child clipped to its container's bounds.The clip will be based on the horizontal gravity: a left gravity will clip the rightedge, a right gravity will clip the left edge, and neither will clip both edges. --><flag name="clip_horizontal" value="0x08"/><!-- Push object to the beginning of its container, not changing its size. --><flag name="start" value="0x00800003"/><!-- Push object to the end of its container, not changing its size. --><flag name="end" value="0x00800005"/></attr></declare-styleable>
自定义组件基类
package cn.com.xxx;import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;import cn.com.xxx.R;class BaseCircleIndicator extends LinearLayout {private final static int DEFAULT_INDICATOR_WIDTH = 5;protected int mIndicatorMargin = -1;protected int mIndicatorWidth = -1;protected int mIndicatorHeight = -1;protected int mIndicatorBackgroundResId;protected int mIndicatorUnselectedBackgroundResId;protected ColorStateList mIndicatorTintColor;protected ColorStateList mIndicatorTintUnselectedColor;protected Animator mAnimatorOut;protected Animator mAnimatorIn;protected Animator mImmediateAnimatorOut;protected Animator mImmediateAnimatorIn;protected int mLastPosition = -1;@Nullableprivate BaseCircleIndicator.IndicatorCreatedListener mIndicatorCreatedListener;public BaseCircleIndicator(Context context) {super(context);init(context, null);}public BaseCircleIndicator(Context context, AttributeSet attrs) {super(context, attrs);init(context, attrs);}public BaseCircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);}@TargetApi(Build.VERSION_CODES.LOLLIPOP)public BaseCircleIndicator(Context context, AttributeSet attrs, int defStyleAttr,int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context, attrs);}private void init(Context context, AttributeSet attrs) {Config config = handleTypedArray(context, attrs);initialize(config);if (isInEditMode()) {createIndicators(3, 1);}}private Config handleTypedArray(Context context, AttributeSet attrs) {Config config = new Config();if (attrs == null) {return config;}TypedArray typedArray =context.obtainStyledAttributes(attrs, R.styleable.BaseCircleIndicator);config.width =typedArray.getDimensionPixelSize(R.styleable.BaseCircleIndicator_ci_width, -1);config.height =typedArray.getDimensionPixelSize(R.styleable.BaseCircleIndicator_ci_height, -1);config.margin =typedArray.getDimensionPixelSize(R.styleable.BaseCircleIndicator_ci_margin, -1);config.animatorResId = typedArray.getResourceId(R.styleable.BaseCircleIndicator_ci_animator,R.animator.scale_with_alpha);config.animatorReverseResId =typedArray.getResourceId(R.styleable.BaseCircleIndicator_ci_animator_reverse, 0);config.backgroundResId =typedArray.getResourceId(R.styleable.BaseCircleIndicator_ci_drawable,R.drawable.white_radius);config.unselectedBackgroundId =typedArray.getResourceId(R.styleable.BaseCircleIndicator_ci_drawable_unselected,config.backgroundResId);config.orientation = typedArray.getInt(R.styleable.BaseCircleIndicator_ci_orientation, -1);config.gravity = typedArray.getInt(R.styleable.BaseCircleIndicator_ci_gravity, -1);typedArray.recycle();return config;}public void initialize(Config config) {int miniSize = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,DEFAULT_INDICATOR_WIDTH, getResources().getDisplayMetrics()) + 0.5f);mIndicatorWidth = (config.width < 0) ? miniSize : config.width;mIndicatorHeight = (config.height < 0) ? miniSize : config.height;mIndicatorMargin = (config.margin < 0) ? miniSize : config.margin;mAnimatorOut = createAnimatorOut(config);mImmediateAnimatorOut = createAnimatorOut(config);mImmediateAnimatorOut.setDuration(0);mAnimatorIn = createAnimatorIn(config);mImmediateAnimatorIn = createAnimatorIn(config);mImmediateAnimatorIn.setDuration(0);mIndicatorBackgroundResId =(config.backgroundResId == 0) ? R.drawable.white_radius : config.backgroundResId;mIndicatorUnselectedBackgroundResId =(config.unselectedBackgroundId == 0) ? config.backgroundResId: config.unselectedBackgroundId;setOrientation(config.orientation == VERTICAL ? VERTICAL : HORIZONTAL);setGravity(config.gravity >= 0 ? config.gravity : Gravity.CENTER);}public void tintIndicator(@ColorInt int indicatorColor) {tintIndicator(indicatorColor, indicatorColor);}public void tintIndicator(@ColorInt int indicatorColor,@ColorInt int unselectedIndicatorColor) {mIndicatorTintColor = ColorStateList.valueOf(indicatorColor);mIndicatorTintUnselectedColor = ColorStateList.valueOf(unselectedIndicatorColor);changeIndicatorBackground();}public void changeIndicatorResource(@DrawableRes int indicatorResId) {changeIndicatorResource(indicatorResId, indicatorResId);}public void changeIndicatorResource(@DrawableRes int indicatorResId,@DrawableRes int indicatorUnselectedResId) {mIndicatorBackgroundResId = indicatorResId;mIndicatorUnselectedBackgroundResId = indicatorUnselectedResId;changeIndicatorBackground();}public interface IndicatorCreatedListener {/*** IndicatorCreatedListener** @param view     internal indicator view* @param position position*/void onIndicatorCreated(View view, int position);}public void setIndicatorCreatedListener(@Nullable BaseCircleIndicator.IndicatorCreatedListener indicatorCreatedListener) {mIndicatorCreatedListener = indicatorCreatedListener;}protected Animator createAnimatorOut(Config config) {return AnimatorInflater.loadAnimator(getContext(), config.animatorResId);}protected Animator createAnimatorIn(Config config) {Animator animatorIn;if (config.animatorReverseResId == 0) {animatorIn = AnimatorInflater.loadAnimator(getContext(), config.animatorResId);animatorIn.setInterpolator(new BaseCircleIndicator.ReverseInterpolator());} else {animatorIn = AnimatorInflater.loadAnimator(getContext(), config.animatorReverseResId);}return animatorIn;}public void createIndicators(int count, int currentPosition) {if (mImmediateAnimatorOut.isRunning()) {mImmediateAnimatorOut.end();mImmediateAnimatorOut.cancel();}if (mImmediateAnimatorIn.isRunning()) {mImmediateAnimatorIn.end();mImmediateAnimatorIn.cancel();}// Diff Viewint childViewCount = getChildCount();if (count < childViewCount) {removeViews(count, childViewCount - count);} else if (count > childViewCount) {int addCount = count - childViewCount;int orientation = getOrientation();for (int i = 0; i < addCount; i++) {addIndicator(orientation);}}// Bind StyleView indicator;for (int i = 0; i < count; i++) {indicator = getChildAt(i);if (currentPosition<=0){currentPosition=0;}if (currentPosition == i) {
//                bindIndicatorBackground(indicator, mIndicatorBackgroundResId, mIndicatorTintColor);bindIndicatorBackground(indicator, mIndicatorBackgroundResId, "mIndicatorTintColor");mImmediateAnimatorOut.setTarget(indicator);mImmediateAnimatorOut.start();mImmediateAnimatorOut.end();} else {bindIndicatorBackground(indicator, mIndicatorUnselectedBackgroundResId, mIndicatorTintUnselectedColor);mImmediateAnimatorIn.setTarget(indicator);mImmediateAnimatorIn.start();mImmediateAnimatorIn.end();}if (mIndicatorCreatedListener != null) {mIndicatorCreatedListener.onIndicatorCreated(indicator, i);}}mLastPosition = currentPosition;}protected void addIndicator(int orientation) {View indicator = new View(getContext());final LayoutParams params = generateDefaultLayoutParams();params.width = mIndicatorWidth;params.height = mIndicatorHeight;if (orientation == HORIZONTAL) {params.leftMargin = mIndicatorMargin;params.rightMargin = mIndicatorMargin;} else {params.topMargin = mIndicatorMargin;params.bottomMargin = mIndicatorMargin;}addView(indicator, params);}public void animatePageSelected(int position) {if (mLastPosition == position) {return;}if (mAnimatorIn.isRunning()) {mAnimatorIn.end();mAnimatorIn.cancel();}if (mAnimatorOut.isRunning()) {mAnimatorOut.end();mAnimatorOut.cancel();}View currentIndicator;if (mLastPosition >= 0 && (currentIndicator = getChildAt(mLastPosition)) != null) {bindIndicatorBackground(currentIndicator, mIndicatorUnselectedBackgroundResId,mIndicatorTintUnselectedColor);mAnimatorIn.setTarget(currentIndicator);mAnimatorIn.start();}View selectedIndicator = getChildAt(position);if (selectedIndicator != null) {
//            bindIndicatorBackground(selectedIndicator, mIndicatorBackgroundResId,
//                    mIndicatorTintColor);bindIndicatorBackground(selectedIndicator, mIndicatorBackgroundResId,"mIndicatorTintColor");mAnimatorOut.setTarget(selectedIndicator);mAnimatorOut.start();}mLastPosition = position;}protected void changeIndicatorBackground() {int count = getChildCount();if (count <= 0) {return;}View currentIndicator;for (int i = 0; i < count; i++) {currentIndicator = getChildAt(i);if (i == mLastPosition) {bindIndicatorBackground(currentIndicator, mIndicatorBackgroundResId, mIndicatorTintColor);} else {bindIndicatorBackground(currentIndicator, mIndicatorUnselectedBackgroundResId, mIndicatorTintUnselectedColor);}}}private void bindIndicatorBackground(View view, @DrawableRes int drawableRes, @Nullable ColorStateList tintColor) {if (tintColor != null) {Drawable indicatorDrawable = DrawableCompat.wrap(ContextCompat.getDrawable(getContext(), drawableRes).mutate());
//            DrawableCompat.setTintList(indicatorDrawable, tintColor);ViewCompat.setBackground(view, indicatorDrawable);ViewGroup.LayoutParams layoutParams = view.getLayoutParams();layoutParams.width = mIndicatorWidth * 2;view.setLayoutParams(layoutParams);} else {ViewGroup.LayoutParams layoutParams = view.getLayoutParams();layoutParams.width = mIndicatorWidth;view.setLayoutParams(layoutParams);view.setBackgroundResource(drawableRes);}}//    private void bindIndicatorBackground(View view, @DrawableRes int drawableRes, @Nullable ColorStateList tintColor) {private void bindIndicatorBackground(View view, @DrawableRes int drawableRes, @Nullable String tintColor) {if (tintColor != null) {Drawable indicatorDrawable = DrawableCompat.wrap(ContextCompat.getDrawable(getContext(), drawableRes).mutate());
//            DrawableCompat.setTintList(indicatorDrawable, tintColor);ViewCompat.setBackground(view, indicatorDrawable);ViewGroup.LayoutParams layoutParams = view.getLayoutParams();layoutParams.width = mIndicatorWidth * 2;view.setLayoutParams(layoutParams);} else {ViewGroup.LayoutParams layoutParams = view.getLayoutParams();layoutParams.width = mIndicatorWidth;view.setLayoutParams(layoutParams);view.setBackgroundResource(drawableRes);}}protected static class ReverseInterpolator implements Interpolator {@Overridepublic float getInterpolation(float value) {return Math.abs(1.0f - value);}}
}
其他辅助类

如果以下辅助类无法满足使用需求,建议直接翻源码抄一些基础配置类过来使用

Config

package cn.com.xxx.indicator;import android.view.Gravity;
import android.widget.LinearLayout;
import androidx.annotation.AnimatorRes;
import androidx.annotation.DrawableRes;public class Config {int width = -1;int height = -1;int margin = -1;@AnimatorRes int animatorResId = R.animator.scale_with_alpha;@AnimatorRes int animatorReverseResId = 0;@DrawableRes int backgroundResId = R.drawable.white_radius;@DrawableRes int unselectedBackgroundId;int orientation = LinearLayout.HORIZONTAL;int gravity = Gravity.CENTER;Config() {}public static class Builder {private final Config mConfig;public Builder() {mConfig = new Config();}public Builder width(int width) {mConfig.width = width;return this;}public Builder height(int height) {mConfig.height = height;return this;}public Builder margin(int margin) {mConfig.margin = margin;return this;}public Builder animator(@AnimatorRes int animatorResId) {mConfig.animatorResId = animatorResId;return this;}public Builder animatorReverse(@AnimatorRes int animatorReverseResId) {mConfig.animatorReverseResId = animatorReverseResId;return this;}public Builder drawable(@DrawableRes int backgroundResId) {mConfig.backgroundResId = backgroundResId;return this;}public Builder drawableUnselected(@DrawableRes int unselectedBackgroundId) {mConfig.unselectedBackgroundId = unselectedBackgroundId;return this;}public Builder orientation(int orientation) {mConfig.orientation = orientation;return this;}public Builder gravity(int gravity) {mConfig.gravity = gravity;return this;}public Config build() {return mConfig;}}
}

scale_with_alpha

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:duration="@android:integer/config_shortAnimTime"><objectAnimatorandroid:propertyName="alpha"android:valueType="floatType"android:valueFrom="0.5"android:valueTo="1.0"/><objectAnimatorandroid:propertyName="scaleX"android:valueType="floatType"android:valueFrom="1.0"android:valueTo="1.8"/><objectAnimatorandroid:propertyName="scaleY"android:valueType="floatType"android:valueFrom="1.0"android:valueTo="1.8"/>
</set>

white_radius

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="oval"><solidandroid:color="@android:color/white"/>
</shape>

CircleIndicator2 核心组件

太久了,忘记有没有改动源码了,现如下,可正常使用

package cn.com.xxx;import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SnapHelper;/*** CircleIndicator2 work with RecyclerView and SnapHelper*/
public class CircleIndicator2 extends BaseCircleIndicator {private RecyclerView mRecyclerView;private SnapHelper mSnapHelper;public CircleIndicator2(Context context) {super(context);}public CircleIndicator2(Context context, AttributeSet attrs) {super(context, attrs);}public CircleIndicator2(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@TargetApi(Build.VERSION_CODES.LOLLIPOP)public CircleIndicator2(Context context, AttributeSet attrs, int defStyleAttr,int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}public void attachToRecyclerView(@NonNull RecyclerView recyclerView,@NonNull SnapHelper snapHelper) {mRecyclerView = recyclerView;mSnapHelper = snapHelper;mLastPosition = -1;createIndicators();recyclerView.removeOnScrollListener(mInternalOnScrollListener);recyclerView.addOnScrollListener(mInternalOnScrollListener);}private void createIndicators() {RecyclerView.Adapter adapter = mRecyclerView.getAdapter();int count;if (adapter == null) {count = 0;} else {count = adapter.getItemCount();}createIndicators(count, getSnapPosition(mRecyclerView.getLayoutManager()));}public int getSnapPosition(@Nullable RecyclerView.LayoutManager layoutManager) {if (layoutManager == null) {return RecyclerView.NO_POSITION;}View snapView = mSnapHelper.findSnapView(layoutManager);if (snapView == null) {return RecyclerView.NO_POSITION;}return layoutManager.getPosition(snapView);}private final RecyclerView.OnScrollListener mInternalOnScrollListener =new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);int position = getSnapPosition(recyclerView.getLayoutManager());if (position == RecyclerView.NO_POSITION) {return;}animatePageSelected(position);}};private final RecyclerView.AdapterDataObserver mAdapterDataObserver =new RecyclerView.AdapterDataObserver() {@Override public void onChanged() {super.onChanged();if (mRecyclerView == null) {return;}RecyclerView.Adapter adapter = mRecyclerView.getAdapter();int newCount = adapter != null ? adapter.getItemCount() : 0;int currentCount = getChildCount();if (newCount == currentCount) {// No changereturn;} else if (mLastPosition < newCount) {mLastPosition = getSnapPosition(mRecyclerView.getLayoutManager());} else {mLastPosition = RecyclerView.NO_POSITION;}createIndicators();}@Override public void onItemRangeChanged(int positionStart, int itemCount) {super.onItemRangeChanged(positionStart, itemCount);onChanged();}@Override public void onItemRangeChanged(int positionStart, int itemCount,@Nullable Object payload) {super.onItemRangeChanged(positionStart, itemCount, payload);onChanged();}@Override public void onItemRangeInserted(int positionStart, int itemCount) {super.onItemRangeInserted(positionStart, itemCount);onChanged();}@Override public void onItemRangeRemoved(int positionStart, int itemCount) {super.onItemRangeRemoved(positionStart, itemCount);onChanged();}@Overridepublic void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {super.onItemRangeMoved(fromPosition, toPosition, itemCount);onChanged();}};public RecyclerView.AdapterDataObserver getAdapterDataObserver() {return mAdapterDataObserver;}
}

加工 核心组件

组件原始效果无法直接满足我们需求,所以需要稍加改变

需修改指针显示组件的以下自定义属性

  • app:ci_drawable 选中状态
  • app:ci_drawable_unselected 默认状态
  • app:ci_margin=“2dp” 边距

默认状态下引用 drawable_market_preferred_fund_l_normal

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle"><sizeandroid:width="6dp"android:height="4dp" /><corners android:radius="@dimen/mp_3" /><solid android:color="#331677FF" />
</shape>

选中状态下引用 drawable_market_preferred_fund_l_selected

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle"><sizeandroid:width="14dp"android:height="4dp" /><corners android:radius="@dimen/mp_3" /><solid android:color="#1760EA" />
</shape>

使用方式

关于RecyclerView使用方式,并不是此处重点,还是继续主讲指示器

首先我们在布局中,先行引入对应组件

<?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="wrap_content"android:layout_marginVertical="@dimen/mp_6"android:orientation="vertical"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="wrap_content"tools:itemCount="3"tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"tools:listitem="@layout/adapter_market_fund_fine_item"tools:orientation="horizontal" /><cn.com.xxx.CircleIndicator2android:id="@+id/indicator"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="8dp"app:ci_animator="@animator/adapter_swiper"app:ci_drawable="@drawable/drawable_market_preferred_fund_l_selected"app:ci_drawable_unselected="@drawable/drawable_market_preferred_fund_l_normal"app:ci_height="@dimen/mp_4"app:ci_margin="@dimen/mp_2"app:ci_width="@dimen/mp_6"/></LinearLayout>

xml 预览效果

在这里插入图片描述

RecyclerView设置中加入以下伪代码即可

可参考最上方提到的 CircleIndicator 使用场景 - RecyclerView (CircleIndicator2)

   //添加滑动居中效果val pagerSnapHelper = PagerSnapHelper()pagerSnapHelper.attachToRecyclerView(recyclerView)//绑定指示器和recyclerViewindicator.attachToRecyclerView(recyclerView, pagerSnapHelper)recyclerView.adapter?.registerAdapterDataObserver(indicator.adapterDataObserver)
http://www.dtcms.com/a/541195.html

相关文章:

  • SQL-leetcode—3475. DNA 模式识别
  • AI搜索优化工具推荐!如何用免费工具帮助内容在AI搜索中抢占排名
  • 【GESP】C++二级真题 luogu-B4411 [GESP202509 二级] 优美的数字
  • 提供虚拟主机服务的网站扬州建设工程信息网站
  • C++ 分治 归并排序 归并排序VS快速排序 力扣 912. 排序数组 题解 每日一题
  • 大语言模型发展脉络
  • Python元编程:理解__metaclass__和元类的力量
  • 快速排序和交换排序详解(含三路划分)
  • android如何在framework层禁止指定包名访问网络
  • 输电线路绝缘子缺陷检测数据集VOC+YOLO格式4061张5类别
  • Git 完全指南:入门篇
  • 上海牛巨微seo关键词优化怎么做网站的seo优化
  • 温州网站制作软件凌晨三点看的片免费
  • 【Java +AI |基础篇day4 数组】
  • 麒麟系统使用-在Sublime中达到滚屏效果
  • 泰州网站关键词优化软件咨询新网站友链
  • 行政还要负责网站建设新媒体吗7000元买一个域名做网站
  • 前端常用的环境 API 清单
  • CreArt 2.5.6| 无限AI图片生成,需要注意的是点创建之后切出去几秒再切回来
  • 现金流量表不平排查方法
  • 深入理解 HTTP Cookie 与 Session:会话管理的核心机制
  • 【Windows】CoInitializeEx 和 CoUninitialize() 必须在同一个线程中调用吗?
  • 网站建设职责要求saas建站平台
  • 优秀国外网站设计赏析郑州企业网站优化哪家便宜
  • 机器学习、深度学习、信号处理领域常用公式速查表
  • 各类服装网站建设软件正版化情况及网站建设情况
  • 服务器端护照识别技术:通过图像预处理、OCR字符识别和智能分析实现高效身份核验
  • 武胜网站建设敬请期待海报
  • 基于Vue的高校教师文件管理系统7h274l7n(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • Surface-Book-3 i7-1065G7-i5-1035G7 黑苹果 EFI