使用 HorizontalScrollView 实现滚动控制

功能要求是屏幕上固定显示 3 个 Layout 项(图片+文字),支持点击切换到选择的 Layout 项,并支持滑动切换到最近的 Layout 项。

最后的效果如下:

下面逐步上代码:

布局文件 activity_main.xml 如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView android:text="" android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <HorizontalScrollView
        android:id="@+id/hsv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbarStyle="outsideInset">
        <cn.steven.hsvimageswitch.HSVLayout
            android:id="@+id/avatar_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </HorizontalScrollView>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/hsv"
        android:layout_marginTop="12dp"
        android:id="@+id/scrollx_tv"/>
    <Button
        android:onClick="onClickScrollX"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/scrollx_tv"
        android:layout_marginTop="12dp"
        android:text="滚动位置"/>
</RelativeLayout>

上面的 HorizontalScrollView 中使用了自定义的 HSVLayout 布局,定义(HSVLayout.java)如下:

public class HSVLayout extends LinearLayout {

    private HSVAdapter adapter;
    private Context context;

    public HSVLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    /**
     * 设置布局适配器
     *
     * @param layoutWidthPerAvatar 指定了每一个 item 的占用宽度
     * @param adapter 适配器
     * @param notify 在点击某一个 item 后的回调
     */
    public void setAdapter(int layoutWidthPerAvatar, HSVAdapter adapter,
                           final INotifySelectItem notify) {
        this.adapter = adapter;

        for (int i = 0; i < adapter.getCount(); i++) {
            final Map<String, Object> map = adapter.getItem(i);
            View view = adapter.getView(i, null, null);

            // 为视图设定点击监听器
            final int finalI = i;
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 点击选择了某一个 Item 视图
                    notify.select(finalI);
                }
            });

            this.setOrientation(HORIZONTAL);

            // 设置固定显示的每个 item 布局的宽度
            this.addView(view, new LinearLayout.LayoutParams(
                    layoutWidthPerAvatar, LayoutParams.WRAP_CONTENT));
        }
    }
}

HSVLayout 中的每一个 视图 item 都是由 HSVAdapter 进行设置的,这个比较简单,只控制了每一个 item 的展示,不影响整个水平滚动视图:

public class HSVAdapter extends BaseAdapter {

    private static final String TAG = "HSV";

    private List<Map<String,Object>> lstAvatars;
    private Context context;
    private int layoutWidthPerAvatar;

    public HSVAdapter(Context context, int layoutWidthPerAvatar){
        this.context=context;
        this.lstAvatars =new ArrayList<Map<String,Object>>();
        this.layoutWidthPerAvatar = layoutWidthPerAvatar;
    }
    @Override
    public int getCount() {
        return lstAvatars.size();
    }

    @Override
    public Map<String,Object> getItem(int location) {
        return lstAvatars.get(location);
    }

    @Override
    public long getItemId(int arg0) {
        return arg0;
    }

    public void addObject(Map<String,Object> map){
        lstAvatars.add(map);
        notifyDataSetChanged();
    }

    @Override
    public View getView(int location, View arg1, ViewGroup arg2) {
        View view = LayoutInflater.from(context).inflate(R.layout.user_avatar,null);
        view.setLayoutParams(new ViewGroup.LayoutParams(layoutWidthPerAvatar,
                ViewGroup.LayoutParams.WRAP_CONTENT));
        TextView tvIndex = (TextView) view.findViewById(R.id.index_tv);
        tvIndex.setText("index-" + String.valueOf(location));
        return view;
    }
}

其对应的布局文件 user_avatar.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">
    <ImageView
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:src="@drawable/avatar"/>
    <TextView
        android:id="@+id/index_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center" />
</LinearLayout>

最后看一下主页面 MainActivity:

public class MainActivity extends ActionBarActivity
    implements INotifySelectItem {

    private static final String TAG = "Main";

    private HorizontalScrollView hsv;
    private HSVLayout layoutAvatar;
    private HSVAdapter adapterAvatar;

    private TextView tvScrollX;

    private int layoutWidthPerAvatar = 0;

    private Integer[] images = {
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar,
            R.drawable.avatar
    };

    // 记录当前居中的头像索引
    private int currentIndex = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int width = DisplayUtil.getScreenWidth(this);
        int layoutWidth = (int) (width - getResources().getDimension(R.dimen.activity_horizontal_margin) * 2);

        // 每一个头像占用的宽度
        layoutWidthPerAvatar = layoutWidth / 3;

        hsv = (HorizontalScrollView) findViewById(R.id.hsv);
        hsv.setOnTouchListener(new View.OnTouchListener() {

            private int lastScrollX = 0;
            private int TouchEventId = -9987832;

            Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == TouchEventId) {
                        if (lastScrollX == hsv.getScrollX()) {
                            // 停止滚动,计算合适的位置(采用四舍五入)
                            int indexScrollTo = Math.round(lastScrollX/(layoutWidthPerAvatar*1.0f));
                            Log.d(TAG, "stop scroll - " + lastScrollX
                                    + "|" + layoutWidthPerAvatar
                                    + "|" + lastScrollX/(layoutWidthPerAvatar*1.0f)
                                    + "|" + indexScrollTo);
                            if (indexScrollTo > 0) {
                                hsv.smoothScrollTo(indexScrollTo*layoutWidthPerAvatar, 0);
                            } else {
                                hsv.smoothScrollTo(0, 0);
                            }
                        } else {
                            handler.sendMessageDelayed(
                                    handler.obtainMessage(TouchEventId), 100);
                            lastScrollX = hsv.getScrollX();
                        }
                    }
                }
            };

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d(TAG, "touch event - action: " + event.getAction()
                        + "|" + event.getX()
                        + "|" + event.getY()
                        + "|" + hsv.getScrollX()
                        + "|" + hsv.getScrollY());
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    handler.sendMessageDelayed(handler.obtainMessage(TouchEventId), 100);
                }
                return false;
            }
        });

        layoutAvatar = (HSVLayout) findViewById(R.id.avatar_layout);
        adapterAvatar = new HSVAdapter(this, layoutWidthPerAvatar);
        for (int i = 0; i < images.length; i++) {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("image", images[i]);
            map.put("index", (i+1));
            adapterAvatar.addObject(map);
        }
        layoutAvatar.setAdapter(layoutWidthPerAvatar, adapterAvatar, this);

        tvScrollX = (TextView) findViewById(R.id.scrollx_tv);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void select(int position) {
        Toast.makeText(this, "select " + String.valueOf(position),
                Toast.LENGTH_SHORT).show();
        if (position > 0) {
            if (currentIndex != position) {
                hsv.smoothScrollTo((position-1)*layoutWidthPerAvatar, 0);
                currentIndex = position;
            }
        }
    }

    public void onClickScrollX(View view) {
        tvScrollX.setText("Scroll.x = " + String.valueOf(hsv.getScrollX()));
    }
}
时间: 2024-08-04 07:01:45

使用 HorizontalScrollView 实现滚动控制的相关文章

HorizontalScrollView水平滚动控件

一.HorizontalScrollView控件只是支持水平滚动,而且它只能包含一个控件,通常是在< HorizontalScrollView >标签中定义了一个<LinearLayout> 标签并且在<LinearLayout>标签中android:orientation属性值设置为horizontal,然后在<LinearLayout>标签中放置多个控件,如果<LinearLayout>标签中的控件所占用的总宽度超出屏幕的宽度,就会出现滚动效

Android控件之HorizontalScrollView 去掉滚动栏

在默认情况下.HorizontalScrollView控件里面的内容在滚动的情况下,会出现滚动栏,为了去掉滚动栏.仅仅须要在<HorizontalScrollView/>里面加一句    android:scrollbars="none". 假设想实如今代码里面,点击左(右)button[btnLeft(btnRight)],滚动栏里面的内容会向左向右滚动[horizontalScrollViewMM]. 代码例如以下: 滚动栏向左滚动: btnLeft.setOnClic

android HorizontalScrollView实现滚动状态监听

网上大部分都是直接调用onScrollChanged(int x, int y, int oldx, int oldy) 这个方法的,实际上只是将这个方法的protected改为public而已,本质上上还是没有什么多大的帮助,不多说,直接上代码 package com.dzc.gallery; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import a

ScrollView垂直滚动控件和HorizontalScrollView水平滚动控件

这个控件的特点就是里面只可以有一个控件,一般我们都是这样来做,外面放一个滚动条控件,里面放一个垂直的线性布局垂直摆放,然后在线性布局里添加控件 http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height=

Android淘宝电影日期滚动栏的实现

最近又有大片上映了,前几天刚看完<末日崩塌>,<侏罗纪世界>又来了,对于大片迷来说是一种福利,所以这几天手机上装了各种电影票团购软件,没办法,同样的电影同样的电影院同样的座位,但是不同的团购软件,价格就不一样.ok,言归正传 在淘宝电影上面有这样一个功能,日期可以滑动,并且选中的是在正中间,效果如下: 看完了,那么问题来了.这个功能怎么实现呢? 我们先来分析一下: 把功能拆分一下来看,如果不能滚动,是不是很好实现?其实就是一个 tab 栏,我在前面的 blog 中Android 快

ScrollView 滚动视图

ScrollView 种类: 1.HorizontalScrollView:水平滚动视图 2.ScrollView:垂直滚动视图(常用类) public class MainActivity extends AppCompatActivity { private TextView text; private ScrollView scrollView; @Override protected void onCreate(Bundle savedInstanceState) { super.onC

Android 控制软键盘

通过设置android:windowSoftInputMode="" 控制展示软键盘 参数分为两类:state* 与 adjust* ,前者设置软键盘的显示与隐藏,后者设置对当前展示页面布局的影响.可以同时设置一个state*与adjust*两个参数,例如:<activity android:windowSoftInputMode="stateVisible|adjustResize" > stateUnspecified 未指定状态,有输入框时显示软键

FMX ScrollBox 拖拽控制

Firemonkey下的ScrollBox 拖拽控制,滚动控制. AniCalculations 仅允许纵向拖拽 scrlbx.AniCalculations.TouchTracking := [System.UITypes.ttVertical]; 锁死不让拖拽. StringGrid1.AniCalculations.TouchTracking :=[]; StringGrid1.AniCalculations.AutoShowing:=false; AniCalculations.Boun

关于横纵向滚动布局

要求 1.横向滚动时,音符轴不动,图形和时间轴滚动 2.纵向滚动时,时间轴不动,图形和音符轴滚动 思路 1.时间轴放上面,溢出隐藏 2.滚动控制,横向滚动时,获取滚动的宽度scrollLeft(),然后赋值给时间轴的margin-left