横向滑动视图HorizontalScrollView精炼详解

发布时间: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

三、HorizontalScrollView添加自动滚动和回弹效果

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,而不会有所变化。

效果如下:

需要做网站?需要网络推广?欢迎咨询客户经理 13272073477