发布时间:2025-12-09 13:53:02 浏览次数:4
由于移动设备物理显示空间一般有限,不可能一次性的把所有要显示的内容都显示在屏幕上。所以各大平台一般会提供一些可滚动的视图来向用户展示数据。Android平台框架中为我们提供了诸如ListView、GirdView、ScrollView、RecyclerView等滚动视图控件,这几个视图控件也是我们平常使用最多的。本节内容我们来分析一下横向滚动视图HorizontalScrollView。
HorizontalScrollView是FrameLayout的子类,这意味着你只能在它下面放置一个子控件,这个子控件可以包含很多数据内容。有可能这个子控件本身就是一个布局控件,可以包含非常多的其他用来展示数据的控件。这个布局控件一般使用的是一个水平布局的LinearLayout 。
本节内容使用HorizontalScrollView分为两种情形:
①横向布局视图中放入文字;
②横向布局视图中放入图片
(1)布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.administrator.hscrollview.MainActivity"> <HorizontalScrollView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#007b12"> <LinearLayout android: android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" /> </HorizontalScrollView> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="TextView_Test" /></RelativeLayout>(2)主Activity代码文件
public class MainActivity extends AppCompatActivity{ private HorizontalScrollView horizontalScrollView; private LinearLayout container; private String cities[] = new String[]{"London", "Bangkok", "Paris", "Dubai", "Istanbul", "New York", "Singapore", "Kuala Lumpur", "Hong Kong", "Tokyo", "Barcelona", "Vienna", "Los Angeles", "Prague", "Rome", "Seoul", "Mumbai", "Jakarta", "Berlin", "Beijing", "Moscow", "Taipei", "Dublin", "Vancouver"}; private ArrayList<String> data = new ArrayList<>(); private TextView testTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_centerlockhorizontalscrollview); bindData(); setUIRef(); bindHZSWData(); }//将集合中的数据绑定到HorizontalScrollView上 private void bindHZSWData() {//为布局中textview设置好相关属性 LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.gravity = Gravity.CENTER; layoutParams.setMargins(20, 10, 20, 10); for (int i = 0; i < data.size(); i++) { TextView textView = new TextView(this); textView.setText(data.get(i)); textView.setTextColor(Color.WHITE); textView.setLayoutParams(layoutParams); container.addView(textView); container.invalidate(); } }//初始化布局中的控件 private void setUIRef() { horizontalScrollView = (HorizontalScrollView) findViewById(R.id.horizontalScrollView); container = (LinearLayout) findViewById(R.id.horizontalScrollViewItemContainer); testTextView = (TextView) findViewById(R.id.testTextView); }//将字符串数组与集合绑定起来 private void bindData() { //add all cities to our ArrayList Collections.addAll(data, cities); }}运行效果如图:
(3)为HorizontalScrollView中的item设置点击事件
在上面的代码中添加两段代码
private void bindHZSWData() {........ for (int i = 0; i < data.size(); i++) { TextView textView = new TextView(this); textView.setText(data.get(i)); textView.setTextColor(Color.WHITE); textView.setLayoutParams(layoutParams); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { performItemClick(view); } });.... } } private void performItemClick(View view) { //------get Display's Width-------- DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int screenWidth = displayMetrics.widthPixels; int scrollX = (view.getLeft() - (screenWidth / 2)) + (view.getWidth() / 2); //smooth scrolling horizontalScrollView horizontalScrollView.smoothScrollTo(scrollX, 0); //additionally we set current center textView data to our testTextView String s = "CenterLocked Item: "+((TextView)view).getText(); testTextView.setText(s); }为了展示显示效果,将每次item中的text设置到界面中,进行显示,运行效果如图:
三、上代码,具体实现图片类的横向布局
(1)主布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.administrator.hscrollview.MainActivity"> <HorizontalScrollView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#007b12"> <LinearLayout android: android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" /> </HorizontalScrollView></RelativeLayout>(2)主Activity代码
public class MainActivity extends AppCompatActivity { private HorizontalScrollView horizontalScrollView; private LinearLayout container; private Integer mImgIds[] = new Integer[]{R.drawable.aa, R.drawable.bb, R.drawable.cc, R.drawable.dd, R.drawable.ee, R.drawable.ff, R.drawable.gg, R.drawable.hh, R.drawable.ii, R.drawable.aaa, R.drawable.bbb, R.drawable.ccc, R.drawable.ddd, R.drawable.eee, R.drawable.fff, R.drawable.ggg, R.drawable.hhh, R.drawable.iii}; private ArrayList<Integer> data = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindData(); setUIRef(); bindHZSWData(); } private void bindHZSWData() { LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.gravity = Gravity.CENTER; layoutParams.setMargins(20, 10, 20, 10); for (int i = 0; i < data.size(); i++) { ImageView imageView = new ImageView(this); imageView.setImageResource(data.get(i)); imageView.setLayoutParams(layoutParams); container.addView(imageView); container.invalidate(); } } //初始化布局中定义的控件 private void setUIRef() { horizontalScrollView = (HorizontalScrollView) findViewById(R.id.horizontalScrollView); container = (LinearLayout) findViewById(R.id.horizontalScrollViewItemContainer); testTextView = (TextView) findViewById(R.id.testTextView); } //将字符串数组中的数据加入到集合当中 private void bindData() { //add all cities to our ArrayList Collections.addAll(data, mImgIds); }}运行效果如图:
当然了,最简单的运用图片类的HorizontalScrollView,就是直接将图片放置在HorizontalScrollView的子布局中进行显示,只需要一个布局文件进行控制,这样做非常简单,UI是通过布局文件进行控制。
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android: android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.administrator.horizontalscrollview.MainActivity"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#ff00ff" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#000000" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#b7a500" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#c1070e" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#ff00ff" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#000000" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#b7a500" /> <ImageView android:layout_width="200dp" android:layout_height="200dp" android:background="#c1070e" /> </LinearLayout> </HorizontalScrollView></RelativeLayout>注意:无论使用何种方式,注意HoriztalScrollview都只有一个直接子view。否则会报错:
Caused by: java.lang.IllegalStateException: HorizontalScrollView can host only one direct child
1)添加自动滚动效果
HorizontalScrollView并没有内置自动滚动的API方法,所以要自己实现,滚动类似平移,所以采用平移动画实现。
private AnimatorSet mAnimatorSetLeft, mAnimatorSetRight;private ObjectAnimator mItemsliding;private ObjectAnimator mItemsAlpha; //初始化布局中的控件 private void setUIRef() { horizontalScrollView = (HorizontalScrollView) findViewById(R.id.horizontalScrollView); UITools.elasticPadding(horizontalScrollView, 300); // 可选 为左右回弹效果实现 //container 为HorizontalScrollView的直接子布局 container = (LinearLayout) findViewById(R.id.horizontalScrollViewItemContainer); mAnimatorSetLeft = new AnimatorSet(); mAnimatorSetRight = new AnimatorSet(); mItemsliding = ObjectAnimator.ofFloat(container,"translationX",0,-300); mItemsAlpha = ObjectAnimator.ofFloat(container,"alpha",1,1); mAnimatorSetLeft.setDuration(0); mAnimatorSetLeft.play(mItemsliding).with(mItemsAlpha); mAnimatorSetLeft.start(); mAnimatorSetLeft.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mItemsliding = ObjectAnimator.ofFloat(container,"translationX",-300,0); mItemsAlpha = ObjectAnimator.ofFloat(container,"alpha",1,1); mAnimatorSetRight.setStartDelay(500); mAnimatorSetRight.setDuration(500); mAnimatorSetRight.play(mItemsliding).with(mItemsAlpha); mAnimatorSetRight.start(); } }); testTextView = (TextView) findViewById(R.id.testTextView); }注意,这里的动画绑定对象不是HoriztalScrollView而是其直接子布局对象container。
效果如下:
2)添加回弹效果
HorizontalScrollView添加回弹效果,有两种方案:①自定义HorizontalScrollView;②使用工具类;
①自定义HorizontalScrollView,使用时直接作为布局元素替换掉旧的HorizontalScrollView即可;
public class BouncyHScrollView extends HorizontalScrollView { private static final int MAX_X_OVERSCROLL_DISTANCE = 200; private Context mContext; private int mMaxXOverscrollDistance; public BouncyHScrollView(Context context) { super(context); // TODO Auto-generated constructor stub mContext = context; initBounceDistance(); } public BouncyHScrollView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub mContext = context; initBounceDistance(); } public BouncyHScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub mContext = context; initBounceDistance(); } private void initBounceDistance(){ final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); mMaxXOverscrollDistance = (int) (metrics.density * MAX_X_OVERSCROLL_DISTANCE); } @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent){ //这块是关键性代码 return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, mMaxXOverscrollDistance, maxOverScrollY, isTouchEvent); }}②工具类;调用代码
public class UITools { /**HorizontalScrollView添加阻尼效果 * ScrollView效果不太好 * 利用父元素的Padding给ScrollView添加弹性 * @param scrollView * @param padding */ public static void elasticPadding(final ScrollView scrollView, final int padding){ View child = scrollView.getChildAt(0); //记录以前的padding final int oldpt = child.getPaddingTop(); final int oldpb = child.getPaddingBottom(); //设置新的padding child.setPadding(child.getPaddingLeft(), padding+oldpt, child.getPaddingRight(), padding+oldpb); //添加视图布局完成事件监听 scrollView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { private boolean inTouch = false; //手指是否按下状态 @SuppressLint("NewApi") private void disableOverScroll(){ scrollView.setOverScrollMode(ScrollView.OVER_SCROLL_NEVER); } /** 滚动到顶部 */ private void scrollToTop(){ scrollView.smoothScrollTo(scrollView.getScrollX(), padding-oldpt); } /** 滚动到底部 */ private void scrollToBottom(){ scrollView.smoothScrollTo(scrollView.getScrollX(), scrollView.getChildAt(0).getBottom()-scrollView.getMeasuredHeight()-padding+oldpb); } /** 检测scrollView结束以后,复原位置 */ private final Runnable checkStopped = new Runnable() { @Override public void run() { int y = scrollView.getScrollY(); int bottom = scrollView.getChildAt(0).getBottom()-y-scrollView.getMeasuredHeight(); if(y <= padding && !inTouch){ scrollToTop(); }else if(bottom<=padding && !inTouch){ scrollToBottom(); } } }; @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { //移除监听器 scrollView.getViewTreeObserver().removeGlobalOnLayoutListener(this); //设置最小高度 //scrollView.getChildAt(0).setMinimumHeight(scrollView.getMeasuredHeight()); //取消overScroll效果 if(Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD){ disableOverScroll(); } scrollView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_POINTER_DOWN){ inTouch = true; }else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL){ inTouch = false; //手指弹起以后检测一次是否需要复原位置 scrollView.post(checkStopped); } return false; } }); scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { if(!inTouch && scrollView!=null && scrollView.getHandler()!=null){//如果持续滚动,移除checkStopped,停止滚动以后只执行一次检测任务 scrollView.getHandler().removeCallbacks(checkStopped); scrollView.postDelayed(checkStopped, 100); } } }); //第一次加载视图,复原位置 scrollView.postDelayed(checkStopped, 300); } }); } /** * 利用父元素的Padding给HorizontalScrollView添加弹性 * @param scrollView * @param padding */ public static void elasticPadding(final HorizontalScrollView scrollView, final int padding){ Log.i("", "elasticPadding>>>>!!"); View child = scrollView.getChildAt(0); //记录以前的padding final int oldpt = child.getPaddingTop(); final int oldpb = child.getPaddingBottom(); //设置新的padding child.setPadding(padding+oldpt, child.getPaddingTop(), padding+oldpb, child.getPaddingBottom()); //添加视图布局完成事件监听 scrollView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { private boolean inTouch = false; //手指是否按下状态 @SuppressLint("NewApi") private void disableOverScroll(){ scrollView.setOverScrollMode(ScrollView.OVER_SCROLL_NEVER); } /** 滚动到左边 */ private void scrollToLeft(){ scrollView.smoothScrollTo(padding-oldpt, scrollView.getScrollY()); } /** 滚动到底部 */ private void scrollToRight(){ scrollView.smoothScrollTo(scrollView.getChildAt(0).getRight()-scrollView.getMeasuredWidth()-padding+oldpb, scrollView.getScrollY()); } /** 检测scrollView结束以后,复原位置 */ private final Runnable checkStopped = new Runnable() { @Override public void run() { int x = scrollView.getScrollX(); int bottom = scrollView.getChildAt(0).getRight()-x-scrollView.getMeasuredWidth(); if(x <= padding && !inTouch){ scrollToLeft(); }else if(bottom<=padding && !inTouch){ scrollToRight(); } } }; @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { //移除监听器 scrollView.getViewTreeObserver().removeGlobalOnLayoutListener(this); //取消overScroll效果 if(Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD){ disableOverScroll(); } scrollView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_POINTER_DOWN){ inTouch = true; }else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL){ inTouch = false; //手指弹起以后检测一次是否需要复原位置 scrollView.post(checkStopped); } return false; } }); scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { //如果持续滚动,移除checkStopped,停止滚动以后只执行一次检测任务 if(!inTouch && scrollView!=null && scrollView.getHandler()!=null){ scrollView.getHandler().removeCallbacks(checkStopped); scrollView.postDelayed(checkStopped, 100); } } }); //第一次加载视图,复原位置 scrollView.postDelayed(checkStopped, 300); } }); }}调用代码:
UITools.elasticPadding(horizontalScrollView, 200);传入HorizontalScrollView对象和一个int类型(表示回弹的距离)的数值即可.
效果如下:
最后补充两个HorizontalScrollView的滚动方法:
HorizontalScrollView属于Scroll类家族成员,自然少不了控制其滚动的方法:
①滚动到指定位置 —— smoothScrollTo(intx, inty);
②滚动指定距离 —— smoothScrollBy(intx, inty);
2019.04.21添加:HorizontalScrollView点击子项自动居中的实现,利用smoothScrollTo ()方法实现:
public class HorCenterActivity extends AppCompatActivity implements View.OnClickListener { private HorizontalScrollView hor; private LinearLayout ll; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_hor); hor = findViewById(R.id.hor); ll = findViewById(R.id.ll); for (int i = 0; i < ll.getChildCount(); i++) { ll.getChildAt(i).setOnClickListener(this); } } // 实现Horizon自动滚动居中 private void autoScroll(int i) { // Width of the screen DisplayMetrics metrics = getResources() .getDisplayMetrics(); int widthScreen = metrics.widthPixels; // Width of one child (Button) int widthChild = ll.getChildAt(i).getWidth(); // 获取对应位置的子View的宽度 // Nb children in screen int nbChildInScreen = widthScreen / widthChild; // Child position left int positionLeftChild = ll.getChildAt(i).getLeft(); // 获取对应位置的子View的左边位置 - 坐标 // Auto scroll to the middle hor.smoothScrollTo((positionLeftChild - ((nbChildInScreen * widthChild) / 2) + widthChild / 2), 0);// hor.smoothScrollTo((positionLeftChild - (widthScreen / 2) + widthChild / 2), 0); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.a: autoScroll(0); break; case R.id.aa: autoScroll(1); break; case R.id.aaa: autoScroll(2); break; case R.id.aaaa: autoScroll(3); break; case R.id.aaaaa: autoScroll(4); break; case R.id.aaaaaa: autoScroll(5); break; case R.id.aaaaaaa: autoScroll(6); break; case R.id.aaaaaaaa: autoScroll(7); break; } }}如上autoScroll()方法,我们传入子项的索引值即可,从0开始,注意,此实现方式不论子项是否可见,索引值都是不变的,比如一共有7个子项,索引值是0~6,然后将前三个子项设为不可见,此时所有子项的索引值仍然是0~6,而不会有所变化。
效果如下: