Android横向滑动加载更多的控件的实现—HorizontalScrollSlideView
需求
之前公司业务要求做一个横向滑动的,可以加载更多的控件,第一时间想到的就是 RecyclerView 来实现 ,后面仔细想想滑动拦截不好控制等等
所以就换了个思路来实现了。
思路:
控件继承自LinearLayout,外面包裹一层HorizontalScrollView,并重写dispatchTouchEvent()事件,当横向滑动到LinearLayout的右边缘滑动到控件的右边缘时,将隐藏的侧拉头跟随手势慢慢拉出。这中间伴随着侧拉头的状态实时的改变。松手时,如果侧拉的距离已经足够多,则回调
OnSlideBottomListener 。当回调结束时,回弹底部箭头,让侧拉头再次隐藏。
大概的思路是这样的:
先上效果图:
先说件很操蛋的事情就是 HorizontalScrollView的滑动监听事件是protected为了在外面能拿到这个滑动监听,所以先把 这个控件重写了把滑动事件先回调回来。
public class ObservableScrollView extends HorizontalScrollView {private OnScrollChangedListener onScrollChangedListener;public ObservableScrollView(Context context) {super(context);}public ObservableScrollView(Context context, AttributeSet attrs) {super(context, attrs);}public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public void setOnScrollListener(OnScrollChangedListener onScrollChangedListener) {this.onScrollChangedListener = onScrollChangedListener;}@Overrideprotected void onScrollChanged(int x, int y, int oldX, int oldY) {super.onScrollChanged(x, y, oldX, oldY);if (onScrollChangedListener != null) {onScrollChangedListener.onScrollChanged(x, y, oldX, oldY);}}public interface OnScrollChangedListener {void onScrollChanged(int x, int y, int oldX, int oldY);}
}
接下来便是我们的主要实现的了:
public class HorizontalScrollSlideView extends LinearLayout implements ObservableScrollView.OnScrollChangedListener {private static final String TAG = "ScrollSlideView";//移动触发步幅private final int MOVE_STRIDE = 6;//记录移动xprivate float mRecodX;//记录偏移量private float mOffsetX;//底部分界线位置private int mBottomParting;//底部展示区长度private int mBottomShow;//底部触发区长度private int mBottomAll;//是否有触摸private boolean isDown = false;private Handler mHandler;//滑动触发的监听private OnSlideBottomListener mOnSlideBottomListener;//内容外部的滑动viewprivate ObservableScrollView mScroolView;//包裹内容viewprivate LinearLayout mContentView;//底部展示viewprivate View mBottomShowView;//底部触发到监听的viewprivate View mBottomGoView;private boolean needScrollBottom = true;public HorizontalScrollSlideView(Context context) {this(context, null);}public HorizontalScrollSlideView(Context context, AttributeSet attrs) {super(context, attrs);mHandler = new Handler();//LayoutInflater.from(context).inflate(R.layout.horizontal_scroll_slide_view, this);//LayoutInflater.from(context).inflate(R.layout.horizontal_scroll_slide_view_bottom, this);ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);mScroolView = new ObservableScrollView(context);mContentView = new LinearLayout(context);mScroolView.setLayoutParams(lp);mContentView.setLayoutParams(new ViewGroup.LayoutParams(lp));mScroolView.addView(mContentView);mScroolView.setHorizontalScrollBarEnabled(false);addView(mScroolView);}/*** 设置滑动区的内容** @param views*/public void setContentViews(List<View> views) {mContentView.removeAllViews();for (View view : views) {mContentView.addView(view);}}public void setContentView(View view) {mContentView.removeAllViews();mContentView.addView(view);}public ViewGroup getContentContainer() {return mContentView;}/*** 设置触发goveiw的监听** @param listener*/public void setOnSlideBottomListener(OnSlideBottomListener listener) {mOnSlideBottomListener = listener;}/*** 覆盖后,返回自定义底部view** @return 底部展现view*/protected View getBottomShowView() {TextView textView = new TextView(getContext());textView.setText("继续滑动\n查看全部");textView.setGravity(Gravity.CENTER);textView.setClickable(false);textView.setEnabled(false);
// textView.setBackgroundColor(getResources().getColor(R.color.colorPrimary));textView.setTextColor(getContext().getResources().getColor(R.color.gray_616161));ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(dp2px(100), ViewGroup.LayoutParams.MATCH_PARENT);textView.setLayoutParams(lp);return textView;}/*** 覆盖后,返回自定义底部触发view** @return 底部触发view*/protected View getBottomGoView() {TextView textView = new TextView(getContext());textView.setText("->");textView.setGravity(Gravity.CENTER);
// textView.setBackgroundColor(getResources().getColor(R.color.colorAccent));textView.setTextColor(getContext().getResources().getColor(R.color.gray_616161));ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(dp2px(20), ViewGroup.LayoutParams.MATCH_PARENT);textView.setLayoutParams(lp);return textView;}@Overrideprotected void onFinishInflate() {super.onFinishInflate();
// mScroolView = findViewById(R.id.sv);
// mContentView = findViewById(R.id.content);//mBottomShowView = findViewById(R.id.bottom_show);//mBottomGoView = findViewById(R.id.bottom_go);mScroolView.setOnScrollListener(this);View showView = getBottomShowView();if (showView != null) {addView(showView);mBottomShowView = showView;}View goView = getBottomGoView();if (goView != null) {addView(goView);mBottomGoView = goView;}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mBottomShow = mBottomShowView.getWidth();mBottomAll = mBottomShow + mBottomGoView.getWidth();mBottomParting = mBottomShow / 2;
// Log.i(TAG, "onmeassure: " + mBottomAll);}@Overridepublic void onScrollChanged(int x, int y, int oldX, int oldY) {if (!isDown && x > oldX && isScrollBottom(true)) {setScrollX(mBottomShow);}}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {
// Log.i(TAG, "dispatch: " + ev.getAction());if (isScrollBottom(true) || getScrollX() > 0) {handleTouch(ev);} else {mRecodX = ev.getX();}if (ev.getAction() == MotionEvent.ACTION_DOWN) {isDown = true;} else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {isDown = false;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {//消费掉,保证dispatchToucheventif (needScrollBottom) {ViewParent parent = this;while (!((parent = parent.getParent()) instanceof ViewPager))parent.requestDisallowInterceptTouchEvent(true);}return true;}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {boolean isIntercept = isScrollContentBottom() && ev.getAction() == MotionEvent.ACTION_MOVE;
// Log.i(TAG, "onInterceptTouchEvent: " + ev.getAction() + " isINtercept:" + isIntercept);if (isIntercept)getParent().requestDisallowInterceptTouchEvent(true);return isIntercept ? true : super.onInterceptTouchEvent(ev);}private boolean isScrollBottom(boolean isIncludeEqual) {int sx = mScroolView.getScrollX();int cwidth = mScroolView.getChildAt(0).getWidth();int pwidth = getWidth();
// Log.i(TAG, "sx: " + sx + "cwidth: " + cwidth + "pwidth: " + pwidth);if (needScrollBottom)return isIncludeEqual ? sx + pwidth >= cwidth : sx + pwidth > cwidth;elsereturn false;}public void setNeedScrollBottom(boolean needScrollBottom) {this.needScrollBottom = needScrollBottom;}private boolean isScrollContentBottom() {return getScrollX() > 0;}private boolean handleTouch(MotionEvent event) {// Log.i(TAG, "handletouch: " + event.getAction());switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mRecodX = event.getX();break;case MotionEvent.ACTION_MOVE:if (mRecodX == 0)mRecodX = event.getX();//移动的距离mOffsetX = (event.getX() - mRecodX);//是否达移动最小值if (Math.abs(mOffsetX) < MOVE_STRIDE) {return true;}//手指左滑boolean isLeft = event.getX() - mRecodX < 0;mRecodX = event.getX();if (isLeft && getScrollX() >= mBottomAll) {setScrollX(mBottomAll);//Log.i(TAG,"1");} else if (!isLeft && getScrollX() <= 0) {setScrollX(0);//Log.i(TAG,"2");} else {setScrollX((int) (getScrollX() - mOffsetX));//Log.i(TAG,"3");}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:if (getScrollX() < mBottomParting) {setScrollX(0);} else {int delay = 0;if (getScrollX() >= mBottomAll - MOVE_STRIDE) {Log.i(TAG, "slide bottom!");if (mOnSlideBottomListener != null) {mOnSlideBottomListener.onSlideBottom();}delay = 1000;}mHandler.postDelayed(new Runnable() {@Overridepublic void run() {setScrollX(mBottomShow);}}, delay);}break;}return true;}int dp2px(int dp) {return (int) (getContext().getResources().getDisplayMetrics().density * dp + 0.5f);}public interface OnSlideBottomListener {void onSlideBottom();}}
使用起来也很简单,就不单独写demo了:
horScrollView.setContentView(contanteView);
horScrollView.setOnSlideBottomListener(new HorizontalScrollSlideView.OnSlideBottomListener() {@Overridepublic void onSlideBottom() {//响应滑动查看更多的事件}});