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去处理itemView
        HorizontalAdapter hadapter = new HorizontalAdapter(mContext,names);
        autoCenterHorizontalScrollView.setAdapter(hadapter);
        autoCenterHorizontalScrollView.setOnSelectChangeListener(new AutoCenterHorizontalScrollView.OnSelectChangeListener() {
            @Override
            public 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;
    }

    @Override
    public int getCount() {
        return names.size();
    }

    @Override
    public 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;
    }

    @Override
    public 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();
    }

    @Override
    public 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的view
        void onSelectStateChanged(RecyclerView.ViewHolder itemView,int position,boolean isSelected);//改变选中状态
    }

    List<RecyclerView.ViewHolder> viewHolders = new ArrayList<>();
    //自己组装itemView
    public 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() {
                @Override
                public 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() {
            @Override
            public 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() {
        @Override
        public 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";

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        Log.i(tag, "left=" + l);
        // 更新ScrollView的滑动位置
        nowScrollLeft = l;
    }

    @Override
    protected int computeHorizontalScrollRange() {
        Log.i(tag, "横向总宽度 computeHorizontalScrollRange:" + super.computeHorizontalScrollRange());
        Log.i(tag, "computeHorizontalScrollRange2:" + (super.computeHorizontalScrollRange() + getScreenWidth(getContext())));
        return super.computeHorizontalScrollRange() + paddingLeft + paddingRight;
    }

    @Override
    protected 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的position
    private 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() {
                    @Override
                    public void run() {
                        smoothScrollTo(offset_target, 0);
                    }
                });
                return;
            }
        }
    }

    //获取当前选中的item的position
    public 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


<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.AutoCenterHorizontalScrollView
	        android:id="@+id/achs_test"
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:clipToPadding="false">
	
	    cn.demomaster.huan.quickdeveloplibrary.widget.AutoCenterHorizontalScrollView>
	
	    <TextView
	        android: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


<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">
    <TextView
        android: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横向滚动的导航,横向滚动居中,点击横向移动居中,横向滚动居中导航)