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;
}
@Override
protected 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;
//记录移动x
private float mRecodX;
//记录偏移量
private float mOffsetX;
//底部分界线位置
private int mBottomParting;
//底部展示区长度
private int mBottomShow;
//底部触发区长度
private int mBottomAll;
//是否有触摸
private boolean isDown = false;
private Handler mHandler;
//滑动触发的监听
private OnSlideBottomListener mOnSlideBottomListener;
//内容外部的滑动view
private ObservableScrollView mScroolView;
//包裹内容view
private LinearLayout mContentView;
//底部展示view
private View mBottomShowView;
//底部触发到监听的view
private 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;
}
@Override
protected 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;
}
}
@Override
protected 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);
}
@Override
public void onScrollChanged(int x, int y, int oldX, int oldY) {
if (!isDown && x > oldX && isScrollBottom(true)) {
setScrollX(mBottomShow);
}
}
@Override
public 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);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//消费掉,保证dispatchTouchevent
if (needScrollBottom) {
ViewParent parent = this;
while (!((parent = parent.getParent()) instanceof ViewPager))
parent.requestDisallowInterceptTouchEvent(true);
}
return true;
}
@Override
public 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;
else
return 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() {
@Override
public 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() {
@Override
public void onSlideBottom() {
//响应滑动查看更多的事件
}
});