Android横向滚动自动居中的HorizontalScrollView

2024-05-10 05:58

本文主要是介绍Android横向滚动自动居中的HorizontalScrollView,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说实话百度没到合适的,就只能自己改改了。

开发目标:

1.支持横向滚动
2.滑动停止能够自动滚动到item中间的位置
3.点击item自动滚动到选中item的位置
4.第一个item和最后一个item要可以滚动到中间位置

Like this:

在这里插入图片描述

思路:

1.HorizontalScrollView改造一下
2.RecycleView也支持横向滚动(SnapHelper这个还玩不转改起来要吃力)
3.Viewpage同样做到横向切换(viewpager+fragment 觉得会麻烦些)

最后选用HorizontalScrollView

android:clipToPadding="false"这个属性意思是能否在padding区域内绘图
默认是true也就是设置padding后可视区域就变小了
如果设置成false则不会影响可视区域,更像是设置的padding没起作用

这是我的代码:

1.activity部分:

  private void initHorizontal() {AutoCenterHorizontalScrollView autoCenterHorizontalScrollView;autoCenterHorizontalScrollView = findViewById(R.id.achs_test);//测试用的随机字符串集合List<String> names =new ArrayList<>();for(int i=0;i<50;i++){String a = ""+i;for(int j=0;j<i%4;j++){a=a+"A";}names.add(a);}//adapter去处理itemViewHorizontalAdapter hadapter = new HorizontalAdapter(mContext,names);autoCenterHorizontalScrollView.setAdapter(hadapter);autoCenterHorizontalScrollView.setOnSelectChangeListener(new AutoCenterHorizontalScrollView.OnSelectChangeListener() {@Overridepublic void onSelectChange(int position) {((TextView) findViewById(R.id.tv_index)).setText("当前"+position);}});autoCenterHorizontalScrollView.setCurrentIndex(39);}

2.HorizontalAdapter(处理itemView)

import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;import java.util.ArrayList;
import java.util.List;import cn.demomaster.huan.quickdevelop.R;
import cn.demomaster.huan.quickdeveloplibrary.widget.AutoCenterHorizontalScrollView;/*** Created by Squirrel桓 on 2018/12/15.*/
public class HorizontalAdapter implements AutoCenterHorizontalScrollView.HAdapter {List<String> names = new ArrayList<>();private Context context;public HorizontalAdapter(Context context, List<String> names) {this.names = names;this.context = context;}@Overridepublic int getCount() {return names.size();}@Overridepublic RecyclerView.ViewHolder getItemView(int i) {View v = LayoutInflater.from(context).inflate(R.layout.item_warp2, null, false);HViewHolder hViewHolder = new HViewHolder(v);hViewHolder.textView.setBackgroundColor(Color.BLACK);hViewHolder.textView.setText(names.get(i));return hViewHolder;}@Overridepublic void onSelectStateChanged(RecyclerView.ViewHolder viewHolder, int position, boolean isSelected) {if (isSelected) {((HViewHolder) viewHolder).textView.setBackgroundColor(Color.RED);} else {((HViewHolder) viewHolder).textView.setBackgroundColor(Color.BLACK);}}public static class HViewHolder extends RecyclerView.ViewHolder {public final TextView textView;public HViewHolder(View itemView) {super(itemView);textView = (TextView) itemView.findViewById(R.id.tv_tab_name);}}
}

3.AutoCenterHorizontalScrollView


import android.content.Context;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;import java.util.ArrayList;
import java.util.List;/*** @author squirrel桓* @date 2018/12/14.* description:Android横向滚动居中的HorizontalScrollView*/
public class AutoCenterHorizontalScrollView extends HorizontalScrollView {public AutoCenterHorizontalScrollView(Context context) {super(context);init();}public AutoCenterHorizontalScrollView(Context context, AttributeSet attrs) {super(context, attrs);init();}public AutoCenterHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}@Overridepublic void addView(View child) {super.addView(child);init();}/*** itemView适配器很随意*/private HAdapter adapter;public static interface HAdapter {int getCount();//获取子view个数RecyclerView.ViewHolder getItemView(int position);//获取指定index的viewvoid onSelectStateChanged(RecyclerView.ViewHolder itemView,int position,boolean isSelected);//改变选中状态}List<RecyclerView.ViewHolder> viewHolders = new ArrayList<>();//自己组装itemViewpublic void setAdapter(final HAdapter adapter) {if(adapter==null||adapter.getCount()==0){return;}this.adapter = adapter;viewHolders.clear();removeAllViews();LinearLayout linearLayout = new LinearLayout(getContext());for (int i = 0; i < adapter.getCount(); i++) {viewHolders.add(adapter.getItemView(i));LinearLayout linearLayout1 = new LinearLayout(getContext());linearLayout1.addView(viewHolders.get(i).itemView);linearLayout1.setTag(i);linearLayout1.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {int index = (int) view.getTag();if (adapter != null) {//处理上一个adapter.onSelectStateChanged(viewHolders.get(lastIndex) ,lastIndex,false);//触发选中事件回调//处理当前选中的adapter.onSelectStateChanged(viewHolders.get(index) ,index,true);//触发选中事件回调lastIndex = index;}smoothScrollTo(getChildCenterPosition(index), 0);//点击某个item滚动到指定位置}});linearLayout.addView(linearLayout1);}addView(linearLayout);init();}/*** 获取item的X位置** @param index* @return*/private int getChildCenterPosition(int index) {offset_current = super.computeHorizontalScrollOffset();if (getChildCount() <= 0) {return 0;}ViewGroup viewGroup = (ViewGroup) getChildAt(0);if (viewGroup == null || viewGroup.getChildCount() == 0) {return 0;}int offset_tmp = 0;for (int i = 0; i < viewGroup.getChildCount(); i++) {View child = viewGroup.getChildAt(i);int child_width = child.getWidth();offset_tmp = offset_tmp + child_width;if (i == index) {offset_target = offset_tmp - child_width / 2 - viewGroup.getChildAt(0).getWidth() / 2;setCurrent(i);return offset_target;}}return 0;}private int paddingLeft = 0;//左侧内边距private int paddingRight = 0;//右侧内边距private float touchDown_X;//判断是否是点击还是滑动来用void init() {//添加触摸事件,滑动事件会触发this.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) {if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {//按下事件记录x坐标touchDown_X = motionEvent.getX();}if (motionEvent.getAction() == MotionEvent.ACTION_UP||motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {//抬起事件判断是否是滑动事件if (touchDown_X != motionEvent.getX()) {//抬起事件则,触发touchDown_X = motionEvent.getX();handler.removeCallbacks(scrollerTask);handler.postDelayed(scrollerTask, delayMillis);}}return false;}});if (getChildCount() <= 0) {return;}ViewGroup viewGroup = (ViewGroup) getChildAt(0);if (viewGroup == null || viewGroup.getChildCount() == 0) {return;}//一下代码是设置padding,实现第一个itemview和最后一个能够居中View first = viewGroup.getChildAt(0);int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);first.measure(w, h);int first_width = first.getMeasuredWidth();View last = viewGroup.getChildAt(viewGroup.getChildCount() - 1);last.measure(w, h);int last_width = last.getMeasuredWidth();paddingLeft = getScreenWidth(getContext()) / 2 - first_width / 2;paddingRight = getScreenWidth(getContext()) / 2 - last_width / 2;setPadding(paddingLeft, getPaddingTop(), paddingRight, getBottom());//设置默认位置setCurrentIndex(currentIndex);}/*** Runnable延迟执行的时间*/private long delayMillis = 100;/*** 上次滑动left,即x*/private long lastScrollLeft = -1;private long nowScrollLeft = -1;private Runnable scrollerTask = new Runnable() {@Overridepublic void run() {if ((nowScrollLeft == lastScrollLeft)) {lastScrollLeft = nowScrollLeft;nowScrollLeft = -1;int index = getCurrentIndex();if (offset_target != offset_current) {Log.d(tag, "offset_target=" + offset_target + ",offset_current=" + offset_current);smoothScrollTo(offset_target, 0);}if (adapter != null&&adapter.getCount()>0&&currentIndex<adapter.getCount()) {//处理上一个adapter.onSelectStateChanged(viewHolders.get(lastIndex) ,lastIndex,false);//触发选中事件回调//处理当前选中的adapter.onSelectStateChanged(viewHolders.get(index) ,index,true);//触发选中事件回调lastIndex = index;}} else {lastScrollLeft = nowScrollLeft;postDelayed(this, delayMillis);}}};/*** 用来判断滚动是否滑动*/private Handler handler = new Handler();String tag = "AutoCenter";@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);Log.i(tag, "left=" + l);// 更新ScrollView的滑动位置nowScrollLeft = l;}@Overrideprotected int computeHorizontalScrollRange() {Log.i(tag, "横向总宽度 computeHorizontalScrollRange:" + super.computeHorizontalScrollRange());Log.i(tag, "computeHorizontalScrollRange2:" + (super.computeHorizontalScrollRange() + getScreenWidth(getContext())));return super.computeHorizontalScrollRange() + paddingLeft + paddingRight;}@Overrideprotected int computeHorizontalScrollOffset() {Log.i(tag, "当前位置 computeHorizontalScrollOffset:" + super.computeHorizontalScrollOffset());return super.computeHorizontalScrollOffset() + paddingLeft;}private int offset_target;//目标位置private int offset_current;//当前位置private int currentIndex = 0;//当前选中的item的positionprivate int lastIndex=0;//上一次选中的位置private void setCurrent(int currentIndex) {this.currentIndex = currentIndex;if (adapter != null&&adapter.getCount()>0&&currentIndex<adapter.getCount()) {//处理上一个adapter.onSelectStateChanged(viewHolders.get(lastIndex) ,lastIndex,false);//触发选中事件回调//处理当前选中的adapter.onSelectStateChanged(viewHolders.get(currentIndex) ,currentIndex,true);//触发选中事件回调lastIndex = currentIndex;if(onSelectChangeListener!=null){onSelectChangeListener.onSelectChange(currentIndex);}}}public void setCurrentIndex(int currentIndex) {setCurrent(currentIndex);if (getChildCount() <= 0) {return ;}ViewGroup viewGroup = (ViewGroup) getChildAt(0);if (viewGroup == null || viewGroup.getChildCount() == 0) {return ;}int offset_tmp = 0;for (int i = 0; i < viewGroup.getChildCount(); i++) {View child = viewGroup.getChildAt(i);int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);child.measure(w, h);int child_width = child.getMeasuredWidth();offset_tmp = offset_tmp + child_width;if (i ==currentIndex) {View child0 = viewGroup.getChildAt(0);child0.measure(w, h);int child_width0 = child0.getMeasuredWidth();offset_target = offset_tmp - child_width / 2 - child_width0 / 2;this.post(new Runnable() {@Overridepublic void run() {smoothScrollTo(offset_target, 0);}});return;}}}//获取当前选中的item的positionpublic int getCurrentIndex() {offset_current = super.computeHorizontalScrollOffset();if (getChildCount() <= 0) {return 0;}ViewGroup viewGroup = (ViewGroup) getChildAt(0);if (viewGroup == null || viewGroup.getChildCount() == 0) {return 0;}int offset_tmp = 0;for (int i = 0; i < viewGroup.getChildCount(); i++) {View child = viewGroup.getChildAt(i);int child_width = child.getWidth();offset_tmp = offset_tmp + child_width;if (offset_tmp > offset_current) {offset_target = offset_tmp - child_width / 2 - viewGroup.getChildAt(0).getWidth() / 2;setCurrent(i);break;}}return currentIndex;}/*** 选中改变时触发回调*/public OnSelectChangeListener onSelectChangeListener;public void setOnSelectChangeListener(OnSelectChangeListener onSelectChangeListener) {this.onSelectChangeListener = onSelectChangeListener;setCurrent(currentIndex);}public static interface OnSelectChangeListener {void onSelectChange( int position);}/*** 获取屏幕宽度** @return*/public static int getScreenWidth(Context context) {return getDisplayMetrics(context).widthPixels;}/*** 获取 DisplayMetrics** @return*/public static DisplayMetrics getDisplayMetrics(Context context) {return context.getResources().getDisplayMetrics();}}

4.R.layout.activity_center_horizental

<?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"tools:context=".sample.CenterHorizontalActivity"><cn.demomaster.huan.quickdeveloplibrary.widget.AutoCenterHorizontalScrollViewandroid:id="@+id/achs_test"android:layout_width="match_parent"android:layout_height="wrap_content"android:clipToPadding="false"></cn.demomaster.huan.quickdeveloplibrary.widget.AutoCenterHorizontalScrollView><TextViewandroid:id="@+id/tv_index"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:textSize="40dp"android:textColor="@color/white"android:background="@color/gray"android:text="ViewPager"/></LinearLayout>

5.R.layout.item_warp2

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="wrap_content"android:orientation="vertical"android:layout_height="wrap_content"tools:ignore="MissingDefaultResource"><TextViewandroid:id="@+id/tv_tab_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:padding="5dp"android:layout_margin="5dp"android:textColor="@color/white"android:background="@color/black"android:gravity="center"/>
</LinearLayout>
说说还遗留的问题吧,

1.滚动结束事件感觉不理的不好,百度到的开启任务定时判断当前位置是否等于上次的位置来确定是否结束。
2.用下面这种方法计算view宽度时会有不准确的时候,不懂原理。

View first = viewGroup.getChildAt(0);int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);first.measure(w, h);int first_width = first.getMeasuredWidth();

这篇关于Android横向滚动自动居中的HorizontalScrollView的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/975625

相关文章

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

JAVA实现Token自动续期机制的示例代码

《JAVA实现Token自动续期机制的示例代码》本文主要介绍了JAVA实现Token自动续期机制的示例代码,通过动态调整会话生命周期平衡安全性与用户体验,解决固定有效期Token带来的风险与不便,感兴... 目录1. 固定有效期Token的内在局限性2. 自动续期机制:兼顾安全与体验的解决方案3. 总结PS

linux部署NFS和autofs自动挂载实现过程

《linux部署NFS和autofs自动挂载实现过程》文章介绍了NFS(网络文件系统)和Autofs的原理与配置,NFS通过RPC实现跨系统文件共享,需配置/etc/exports和nfs.conf,... 目录(一)NFS1. 什么是NFS2.NFS守护进程3.RPC服务4. 原理5. 部署5.1安装NF

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

在Android中使用WebView在线查看PDF文件的方法示例

《在Android中使用WebView在线查看PDF文件的方法示例》在Android应用开发中,有时我们需要在客户端展示PDF文件,以便用户可以阅读或交互,:本文主要介绍在Android中使用We... 目录简介:1. WebView组件介绍2. 在androidManifest.XML中添加Interne

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按