滑动选项View
额…其实这个我也不知道应该叫做什么,就是滑动出来的选项,就像ios版微信那样,向左滑动,滑出选项,看着就想试试模仿一下,下面来看看如何写。
首先制定思路,滑动出来的选项是一部分,作为主体内容的是一部分,也就是说这是有两部分组成,那么集成FrameLayout,初始化的时候添加两个子view一个是主题内容的viewGroup,一个是装载选项的viewGroup,我在这里定义为三个选项,放在选项viewGroup中(下文为BtnLayout),一个是确定键,一个是取消键,一个是更多键,当然文字是可以修改的,而且选项是否显示也能控制,那么就完成第一步了,下一步就是测量,其实我这里的测量的方法不是很好,主要是在我没有重写各种addView(),这样就没有限制子view的个数,会破坏思路中的两个组成部分,我就在onMeasure()这里限制,因为初始化的时候添加了两个组成部分,所以如果有第三个view,那么这个view就会添加到主题内容viewGroup中(下文为content),然后多余的就移除掉,这样实属不好,这里设置宽度为充满父布局,高度默认为70dp,贴上代码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
? MeasureSpec.getSize(heightMeasureSpec) : SizeUtils.dp2px(context, 70);
btnLayout.setLayoutParams(new LayoutParams(-scroll, height));
for (int i = 2; i < getChildCount(); i++) {
if (i == 2) {
View view = getChildAt(i);
removeView(view);
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
if (layoutParams.width == LayoutParams.MATCH_PARENT) {
layoutParams.width = width;
}
if (layoutParams.height == LayoutParams.MATCH_PARENT) {
layoutParams.height = height;
}
content.addView(view, layoutParams);
} else {
removeView(getChildAt(i));
}
}
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(width, height);
}
上面可以看到这句
btnLayout.setLayoutParams(new LayoutParams(-scroll, height));
这句就是BtnLayout的设置大小的,height就是它的父布局的高度,而宽度就是滑动的距离,记录开始点,结束的,这样计算出滑动距离,因为向左滑动是负数,所以scroll加了个负号。
测量完之后就是布局了,这样的流程估计大家都很熟悉了,其实布局也没什么,不测量还简单,直接是将contentLayout和BtnLayout的位置放好就可以了,贴代码:
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
for (int j = 0; j < getChildCount(); j++) {
View view = getChildAt(j);
if (view == btnLayout) {
btnLayout.layout(width + scroll, 0, width, height);
} else if (view == content) {
content.layout(scroll, 0, width + scroll, height);
}
}
}
他们的位置是根据滑动的距离改变的,而且是横向改变,当scroll为0 的时候,content充满父布局,当向左滑动的时候,content向左滑动,向左scroll为负数,所以相加就好了。
滑动主要用到dispatchTouchEvent(),onInterceptTouchEvent()和onTouchEvent()这三个方法,首先重写onInterceptTouchEvent(),判断什么情况什么时候拦截,当action为down的时候记录点下的x坐标,action为move的时候,scroll=x-start+getScroll(),其中getScroll()是根据是否打开了BtnLayout返回滑动了多少距离,比如没打开的时候,scroll的初始值是0,打开的时候,BtnLayout就是已经展示出来的了,那么就已经是滑动了BtnLayout的宽度,所以scroll的初始值就是btnLayoutd宽度,当action为up的时候就将start和scroll置为默认值,其实在这里的scroll的意义就是判断是否拦截,因为只是想点击的话就没必要拦截了,不然BtnLayout收不到分发的事件,在打开BtnLayout的时候,当scroll滑动距离大于5dp就判定为想要滑动,然后判断滑动的位置是在content还是BtnLayout,在content的话就是直接拦截,我还加了个判断是是否允许在BtnLayout滑动关闭BtnLayout,是的话就拦截,不是的话就不拦截,走super.onInterceptTouchEvent()继续分发,当BtnLayout没有打开的时候,就直接拦截了,因为不会影响到分发给BtnLayout,贴上代码:
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
start = (int) ev.getX();
scroll = start;
break;
case MotionEvent.ACTION_MOVE:
scroll = (int) (ev.getX() - start + getScroll());
break;
case MotionEvent.ACTION_UP:
start = 0;
scroll = getScroll();
break;
}
//判断是否打开BtnLayout
if (isOpenBtn) {
//滑动距离超过5dp判定用户意向为滑动
if (scroll != start && scroll >= getScroll() + SizeUtils.dp2px(context, 5)) {
//起点在contentView部分是直接滑动,在BtnLayout部分要判断是否可以滑动关闭
if (start <= getMeasuredWidth() - getBtnWidth()) {
return true;
} else if (isBtnClose) {
return true;
}
}
} else if (scroll != start && scroll <= getScroll() - SizeUtils.dp2px(context, 5)) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
那么写完是否拦截就改重写onTouchEvent(),如何消费,当拦截之后就要消费这个事件,首先也是跟拦截一样,action是down的时候就记录start的坐标,而且加入是在recyclerView之类的item布局中,那么就不让父布局拦截这个事件,使得这个事件自己消费,当action为move的时候,先判断用户是纵向滑动还是横向滑动,如果是纵向滑动就不拦截父布局,让父布局消费,反正继续自己消费,然后根据是否打开BtnLayout来刷新界面,当action为up的时候,判断滑动距离是否超过BtnLayout的宽度的三分之一,是的话且是向左滑动,也就是想打开BtnLayout,向右滑动的话就是想关闭BtnLayout,那么就自身滑动过去,这时候用的是属性动画,贴上代码:
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
start = (int) ev.getX();
getParent().requestDisallowInterceptTouchEvent(true);
return true;
case MotionEvent.ACTION_MOVE:
//超过一定距离,判断用户想要上下滑动
if (isTB(ev.getRawY())) {
getParent().requestDisallowInterceptTouchEvent(false);
return false;
} else {
getParent().requestDisallowInterceptTouchEvent(true);
}
scroll = (int) (ev.getX() - start + getScroll());
if (isOpenBtn) {
if (scroll < getScroll()) {
scroll = getScroll();
} else if (scroll > 0) {
scroll = 0;
}
} else {
if (scroll > getScroll()) {
scroll = getScroll();
} else if (scroll < -getBtnWidth()) {
scroll = -getBtnWidth();
}
}
requestLayout();
return true;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (isOpenBtn) {
if (scroll >= -getBtnWidth() / 3 * 2) {
isOpenBtn = false;
if (btnChangeListener != null) {
btnChangeListener.onChange(false);
}
normalPosition(scroll, getScroll());
} else {
normalPosition(scroll, -getBtnWidth());
}
} else {
if (scroll <= -getBtnWidth() / 3) {
isOpenBtn = true;
if (btnChangeListener != null) {
btnChangeListener.onChange(true);
}
normalPosition(scroll, getScroll());
} else {
normalPosition(scroll, 0);
}
}
return true;
}
return true;
}
动画代码:
private void normalPosition(int start, int end) {
animator = ValueAnimator.ofInt(start, end);
animator.setDuration(Math.abs(start - end));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
ScrollView.this.start = 0;
scroll = (int) valueAnimator.getAnimatedValue();
requestLayout();
}
});
animator.start();
}
基本上就这些是核心,还有的就是是否分发这个方法没重写,在这里我只是做了当动画在执行时就不分发,返回false,让父布局消费
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (animator != null && animator.isRunning()) {
return false;
}
return super.dispatchTouchEvent(ev);
}
好了,基本就是这样了,我还写了两个方法,一个是打开,一个是关闭,不过没有写成是有动画的打开关闭
public void openBtn() {
if (!isOpenBtn) {
if (btnChangeListener != null) {
btnChangeListener.onChange(true);
}
}
isOpenBtn = true;
scroll = getScroll();
requestLayout();
}
public void closeBtn() {
if (isOpenBtn) {
if (btnChangeListener != null) {
btnChangeListener.onChange(false);
}
}
isOpenBtn = false;
scroll = getScroll();
requestLayout();
}
以上就是滑动选项view的主要代码,我也在自己的练习项目使用,目前没发现什么问题,如大家发现问题可以指正,如果能帮到有需要帮助的人就更好了。
也可以添加我微信互相讨论哦!
wx:Zhang---JY