愿历尽千帆 归来仍少年

基于开源框架SlidingUpPanel二次开发

字数统计: 1,736阅读时长: 7 min
2018/05/14

项目需求
写一个SystemUI,默认是隐藏,手指在Launcher界面顶部往下滑动时,SystemUI显示并随着手指滑动高度不断变化,直到往下铺满屏幕

初步分析
最初的想法是在原生SystemUI基础上进行修改,在看了原生SystemUI代码后,word天,原生SystemUI代码量十分庞大且代码结构比较复杂,很多地方耦合比较高,初步估计厘清需要耗费的时间会比较长。于是有了单独写一个的想法,这样代码比较简洁,以后项目上也方便复用。

设计框架
下拉面板显然需要一个自定义布局,这个自定义View高度能够手指滑动变化,ok,理论上并不复杂
github兜一圈,看看有没有类似的框架,茫茫大海中找到了一个SlidingUpPanel的开源框架
github主页地址: https://github.com/umano/AndroidSlidingUpPanel
确认过眼神,是需要找的框架,看了下介绍是一个能够向上滑动的时候往上飞出一个显示区域的控件,star较多且仍在维护,是个靠谱青年,so,就在此基础上进行修改吧,就这么愉快的决定了。

源码效果图












最开始下面有一个栏目布局,随着手指拖动高度不断变化直到往上完全铺满屏幕

Step1:研究框架源码
源码中有三支主要文件,如下









其中核心文件是SlidingUpPanelLayout.java,对应的是面板的UI界面,继承自ViewGroup,整支文件有1490行之多,好在代码结构清晰明了,阅读起来比较顺利,后面的修改主要针对SlidingUpPanelLayout这支文件以及布局文件

Step2:实做部分
在阅读了源码流程之后,开始撸起袖子干活,主要的修改有下面一些点

面板默认状态修改
PanelState提供的面板五种不同状态

1
2
3
4
5
6
7
public enum PanelState {
EXPANDED, //全部展开状态
COLLAPSED, //默认状态
ANCHORED, //锚点
HIDDEN, //隐藏状态
DRAGGING //拖动状态
}

我们的systemUI样式只需要其中的EXPANDEDHIDDENDRAGGING这三种状态,其他两种状态相关牵扯到的代码都可以移除
源码默认的状态是COLLAPSED

1
2
3
4
/**
* Default initial state for the component
*/
private static PanelState DEFAULT_SLIDE_STATE = PanelState.COLLAPSED;
1
private PanelState mSlideState = DEFAULT_SLIDE_STATE;

构造函数中从配置值获取了默认状态

1
mSlideState = PanelState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_umanoInitialState, DEFAULT_SLIDE_STATE.ordinal())];

后面onMeasure,onLayout将根据mSlideState得值来绘制初始化界面

我们systemUI初始化界面默认需要是HIDDEN状态,故后面更改xml中的配置值调整为hidden即可

去掉一栏布局部分(红色部分)












对应PanelHeight这个的值,这个值在构造函数中会去xml中获取

1
mPanelHeight = ta.getDimensionPixelSize(R.styleable.SystemUiPanel_umanoPanelHeight, -1);

将布局文件中SlidingUpPanelLayout节点下的umanoPanelHeight样式值改为0dp即可

默认划出面板方向
github上给的demo例子里,是将SlidingUpPanelLayout设置android:gravity="bottom",改为top即可

添加手势事件
源码中面板默认不是隐藏,有一定高度,根据点击事件响应展开还是隐藏,我们的systemUI中面板默认是完全隐藏状态,所以就没有了可见的view来响应事件,需要依据手指在顶部滑动的方向以及滑动距离来判断是否打开面板
SlidingUpPanelLayout这支文件中添加手势滑动事件判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean onLauncherTouchEvent(MotionEvent ev, boolean result) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
pointDownY1 = ev.getY();
return result;
case MotionEvent.ACTION_MOVE:
pointDownY2 = ev.getY();
if (pointDownY2 - pointDownY1 > FLIP_DISTANCE && pointDownY1 < FLIP_DISTANCE) {
setPanelState(PanelState.EXPANDED);
}
return result;
default:
return result;
}
}

隐藏状态下无法划出面板
源码设计中对于隐藏状态是无法划出面板的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//省略部分代码
switch (action) {
case MotionEvent.ACTION_DOWN: {
mIsUnableToDrag = false;
mInitialMotionX = x;
mInitialMotionY = y;
if (!isViewUnder(mDragView, (int) x, (int) y)) {
mDragHelper.cancel();
mIsUnableToDrag = true;
return false;
}
break;
}
//省略部分代码
}

将上面代码中if代码块全部注释掉
并将如下

1
2
3
public boolean isTouchEnabled() {
return mIsTouchEnabled && mSlideableView != null && mSlideState != PanelState.HIDDEN;
}

去掉mSlideState != PanelState.HIDDEN判断条件
因为源码设计是当面板状态为hidden时,isTouchEnabled返回false,而setDragView中滑动面板前会先判断isTouchEnabled值,如果为false,直接return

存在概率性卡在半拉状态
项目上线后测试反馈过一个问题,测试步骤比较风骚,手指按住不放下拉到一半左右位置快速转过一个弧度上滑并松开,概率性出现面板卡在一个点上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void onViewDragStateChanged(int state) {
if (mDragHelper != null && mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
mSlideOffset = computeSlideOffset(mSlideableView.getTop());
//applyParallaxForCurrentSlideOffset();
if (mSlideOffset == 1) {
updateObscuredViewVisibility();
setPanelStateInternal(PanelState.EXPANDED);
} else if (mSlideOffset < 0) {
setPanelStateInternal(PanelState.HIDDEN);
mSlideableView.setVisibility(View.INVISIBLE);
} else if (mSlideOffset == 0) {
setPanelStateInternal(PanelState.HIDDEN);
mSlideableView.setVisibility(View.INVISIBLE);
} else {
setPanelStateInternal(PanelState.ANCHORED);
mSlideableView.setVisibility(View.INVISIBLE);
}
}
}

问题复现时,mSlideOffset大于0且小于1,此时会走到setPanelStateInternal(PanelState.ANCHORED);即将面板抛锚到固定点上,界面上表现为卡在一个半拉的状态,这显然不是我们的systemUI需要的,将此处更改为setPanelStateInternal(PanelState.HIDDEN);

核心API
addPanelSlideListener(面板状态的监测)其中复写两个方法:onPanelSlide(获取到偏移量)和onPanelStateChanged(获取面板状态)
界面层主要用到了这个方法

移除无关代码
如下修改均针对SlidingUpPanelLayout这支文件
1.将setDragView中将对mDragView的点击事件相关逻辑全部移除
2.构造函数中将setgravity相关代码移除
3.移除shadow所有的相关的代码
4.移除ANCHORED相关的代码逻辑,因为我们的systemUI并不要在下拉过程中卡在某一高度
5.移除drawChild以及draw(Canvas c)代码

布局文件
如下是针对我们的项目定制修改后的布局(项目名相关打了马赛克,我也不知道为啥这么做,反正就是这么做了)



















id为dragView的内容代表滑动的内容,id为viewpager代表的是面板隐藏后的界面内容即不随手指滑动的内容

注:
SlidingUpPanelLayout为布局根元素,请确保它有且只有两个子view,第一个是主要布局,即固定不随手指滑动的区域,第二个滑动面板布局
如果子VIew不为2,则会抛出异常

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //省略部分代码
     final int childCount = getChildCount();
     if (childCount != 2) {
         throw new IllegalStateException("Sliding up panel layout must have exactly 2 children!");
     }
    //省略部分代码
 }

起初遇到这个异常,看了源码才知道有这个限制,不过可以修改源码突破这个这个限制,有源码了不起嘛?嗨呀嗨呀,十分了不起,有源码真的可以为所欲为,哈哈哈


住所,晚上,汗衫,电脑前,听着歌,胖橘在门外叫
CATALOG