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

相关文章

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

一文详解MySQL如何设置自动备份任务

《一文详解MySQL如何设置自动备份任务》设置自动备份任务可以确保你的数据库定期备份,防止数据丢失,下面我们就来详细介绍一下如何使用Bash脚本和Cron任务在Linux系统上设置MySQL数据库的自... 目录1. 编写备份脚本1.1 创建并编辑备份脚本1.2 给予脚本执行权限2. 设置 Cron 任务2

MySQL 横向衍生表(Lateral Derived Tables)的实现

《MySQL横向衍生表(LateralDerivedTables)的实现》横向衍生表适用于在需要通过子查询获取中间结果集的场景,相对于普通衍生表,横向衍生表可以引用在其之前出现过的表名,本文就来... 目录一、横向衍生表用法示例1.1 用法示例1.2 使用建议前面我们介绍过mysql中的衍生表(From子句

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis