先看看最终实现的效果
写这个主要练习一下学习的自定义viewgroup和viewdraghelper,就当自己记录一下中间遇到的问题。
1. 重写viewgroup的基本步骤 自定义流式布局
2. viewdraghelper的基本用法 洪洋大神的博客
了解了这些,我们就来开始我们的slidingDrawer之旅 。
分析slidingDrawer效果,slidingdrawer包含两部分,一部分为content就是我们的主体内容,一般都是match_parent,另一部分就是我们的抽屉了,抽屉里面包含一个手柄handler的view,包括滑动方向,那么我们首先先定义几个属性
<declare-styleable name="MyDrawer">
<attr name="content" format="reference" /> //主体view
<attr name="drag" format="reference" /> //拖动view
<attr name="handler" format="reference" /> //手柄view
<attr name="isOpen" format="boolean" /> //是否打开
<attr name="dragtype"/> //拖动方向
</declare-styleable>
<attr name="dragtype">
<enum name="left" value="0"/> //从左边滑出来
<enum name="right" value="1"/> //从右边滑出来
<enum name="top" value="2"/> //从顶部滑出来
<enum name="bottom" value="3"/> //从底部滑出来
</attr>
slidingDrawer只支持两个方向的滑动,我们支持4个方向,先定义出来,以后我们用到。
先上完整的代码然后我们再分析。
public class MyDrawer extends ViewGroup {
private final String TAG = "MyDrawer";
private static final int TYPE_LEFT = 0; //左边滑出
private static final int TYPE_RIGHT = 1; //右边滑出
private static final int TYPE_TOP = 2; //上边滑出
private static final int TYPE_BOTTOM = 3; //下边滑出
private ViewDragHelper viewDragHelper; //滑动处理的主要类
/** 自定义属性CUSTOM START **/
private boolean isOpen = true; //是否打开
private int dragType = 3; //默认从下往上滑
/** 自定义属性CUSTOM END **/
private int mContentViewID = -1; //主体viewID
private View mContentView; //主体view
private int mDragContentViewID = -1; //要拖动的viewID
private View mDragContextView; //要拖动的view
private int mHandlerID = -1; //手柄ViewID
private View mHandler; //手柄View
private int myWidth = 0; //父类宽度
private int myHeight = 0; //父类高度
private int dragContentWidth = 0; //拖动view的宽度
private int dragContentHeight = 0; //拖动view的高度
private int handlerWidth = 0; //手柄宽度
private int handlerHeight = 0; //手柄高度
private int surPlusWidth = 0; //减去手柄剩余的宽度
private int surPlusHeight = 0; //减去手柄剩余的高度
private DefaultDragHelper defaultDragHelper;
private MyDrawerListener myDrawerListener = null;
//初始化拖动view的坐标
private int intitLeft = 0;
private int initRight = 0;
private int initTop = 0;
private int initBottom = 0;
public interface MyDrawerListener{
public void open(); //打开
public void close(); //关闭
}
//打开
public void open(){
defaultDragHelper.open();
}
//关闭
public void close(){
defaultDragHelper.close();
}
public void setIsOpen(boolean isOpen){
this.isOpen = isOpen;
}
public boolean getIsOpen(){
return isOpen;
}
public void setMyDrawerListener(MyDrawerListener myDrawerListener) {
this.myDrawerListener = myDrawerListener;
}
public View getHandlerView(){
if(mHandlerID != -1){
return mHandler;
}
return null;
}
public View getDragContextView(){
if(mDragContentViewID != -1){
return mDragContextView;
}
return null;
}
public MyDrawer(Context context) {
super(context);
init(null, 0);
}
public MyDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MyDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
final TypedArray a = getContext().obtainStyledAttributes(
attrs, R.styleable.MyDrawer, defStyle, 0);
/** * * 下面这坨都是自定义属性 详情请看attrs_my_drawer解释 ~\(≧▽≦)/~ */
mContentViewID = a.getResourceId(R.styleable.MyDrawer_content,-1);
mDragContentViewID = a.getResourceId(R.styleable.MyDrawer_drag,-1);
mHandlerID = a.getResourceId(R.styleable.MyDrawer_handler, -1);
isOpen = a.getBoolean(R.styleable.MyDrawer_isOpen, false); //默认为关闭
dragType = a.getInt(R.styleable.MyDrawer_dragtype, TYPE_BOTTOM); //默认为从下边滑出
defaultDragHelper = new DefaultDragHelper();
viewDragHelper = ViewDragHelper.create(this, 1.0f, defaultDragHelper);
}
@Override
public void computeScroll() {
if(viewDragHelper.continueSettling(true)){
postInvalidate();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(mContentViewID == -1){
Log.e(TAG,TAG+"的content属性不能为空");
return;
}
if(mDragContentViewID == -1){
Log.e(TAG,TAG+"的drag属性不能为空");
return;
}
//手柄可以为空
if(mHandlerID != -1){
mHandler = this.findViewById(mHandlerID);
}
mContentView = this.findViewById(mContentViewID);
mDragContextView = this.findViewById(mDragContentViewID);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.e(TAG, "onMeasure");
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
// 计算自定义的ViewGroup中所有子控件的大小
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 设置自定义的控件MyViewGroup的大小
setMeasuredDimension(measureWidth, measureHeight);
MarginLayoutParams paramsDrag = (MarginLayoutParams)mDragContextView.getLayoutParams();
final int contentWidthSpec = getChildMeasureSpec(widthMeasureSpec,paramsDrag.leftMargin+paramsDrag.rightMargin,paramsDrag.width);
final int contentHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsDrag.topMargin + paramsDrag.bottomMargin, paramsDrag.height);
mDragContextView.measure(contentWidthSpec,contentHeightSpec);
MarginLayoutParams paramsContent = (MarginLayoutParams)mContentView.getLayoutParams();
final int dragWidthSpec = getChildMeasureSpec(widthMeasureSpec, paramsContent.leftMargin + paramsContent.rightMargin, paramsContent.width);
final int dragHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsContent.topMargin + paramsContent.bottomMargin, paramsContent.height);
mContentView.measure(dragWidthSpec, dragHeightSpec);
}
private int measureWidth(int pWidthMeasureSpec) {
int result = 0;
int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);// 得到模式
int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);// 得到尺寸
switch (widthMode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = widthSize;
break;
}
return result;
}
private int measureHeight(int pHeightMeasureSpec) {
int result = 0;
int heightMode = MeasureSpec.getMode(pHeightMeasureSpec);
int heightSize = MeasureSpec.getSize(pHeightMeasureSpec);
switch (heightMode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = heightSize;
break;
}
return result;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.e(TAG,"onLayout");
myWidth = getWidth();
myHeight = getHeight();
dragContentWidth = mDragContextView.getMeasuredWidth();
dragContentHeight = mDragContextView.getMeasuredHeight();
if(mHandler != null){
handlerWidth = mHandler.getMeasuredWidth();
handlerHeight = mHandler.getMeasuredHeight();
}
//减去手柄的宽度和高度
surPlusWidth = dragContentWidth - handlerWidth;
surPlusHeight = dragContentHeight - handlerHeight;
//默认里面只有两个view
//设置contentview的位置
MarginLayoutParams contentParam = (MarginLayoutParams) mContentView.getLayoutParams();
mContentView.layout(contentParam.leftMargin, contentParam.topMargin, contentParam.leftMargin+mContentView.getMeasuredWidth(), contentParam.topMargin+mContentView.getMeasuredHeight());
//判断滑动方向和默认是否打开来确定滑动view的具体位置
MarginLayoutParams cParams = (MarginLayoutParams) mDragContextView.getLayoutParams();
//计算坐标真烦
if(dragType==TYPE_LEFT){
if(isOpen){
intitLeft = cParams.leftMargin;
initRight = intitLeft + dragContentWidth;
}else{
intitLeft = -surPlusWidth;
initRight = intitLeft + dragContentWidth;
}
initTop = cParams.topMargin;
initBottom = initTop + dragContentHeight;
}else if(dragType==TYPE_RIGHT){
if(isOpen){
intitLeft = myWidth-dragContentWidth;
initRight = intitLeft + dragContentWidth;
}else{
intitLeft = myWidth-handlerWidth;
initRight = intitLeft + dragContentWidth;
}
initTop = cParams.topMargin;
initBottom = initTop + dragContentHeight;
}else if(dragType==TYPE_TOP){
if(isOpen){
initTop = cParams.topMargin;
initBottom = initTop+dragContentHeight;
}else{
initTop = -surPlusHeight;
initBottom = initTop + dragContentHeight;
}
intitLeft = cParams.leftMargin;
initRight = intitLeft + dragContentWidth;
}else if(dragType==TYPE_BOTTOM){
if(isOpen){
initTop = myHeight - dragContentHeight;
initBottom = initTop + dragContentHeight;
}else{
initTop = myHeight-handlerHeight;
initBottom = myHeight + surPlusHeight;
}
intitLeft = cParams.leftMargin;
initRight = intitLeft + dragContentWidth;
}
mDragContextView.layout(intitLeft, initTop, initRight, initBottom);
}
@Override
protected LayoutParams generateDefaultLayoutParams()
{
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
public LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new MarginLayoutParams(getContext(), attrs);
}
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
{
return new MarginLayoutParams(p);
}
private class DefaultDragHelper extends ViewDragHelper.Callback{
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(child == mDragContextView){
return true;
}
return false;
}
//手指释放的时候回调
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if(dragType==TYPE_LEFT){
int top = mDragContextView.getLeft();
int critical = (int)(- surPlusWidth/2.0f);
if(top>=critical){
//打开
open();
}else{
close();
}
}
if(dragType==TYPE_RIGHT){
if(mDragContextView.getLeft()<=myWidth-dragContentWidth+(surPlusWidth/2.0f)){
//打开
open();
}else{
close();
}
}
if(dragType==TYPE_TOP){
int top = mDragContextView.getTop();
int critical = (int)(- surPlusHeight/2.0f);
if(top>=critical){
//打开
open();
}else{
close();
}
}
if(dragType==TYPE_BOTTOM){
int top = mDragContextView.getTop();
int critical = (int)(myHeight - surPlusHeight/2.0f - handlerHeight);
if(top<=critical){
//打开
open();
}else{
close();
}
}
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if(dragType==TYPE_LEFT){
if(left<-surPlusWidth){
return -surPlusWidth;
}else if(left>0){
return 0;
}
return left;
}
if(dragType==TYPE_RIGHT){
if(left<myWidth-dragContentWidth){ //说明已经拖到左边了
return myWidth-dragContentWidth;
}else if(left>myWidth - handlerWidth){ //说明已经拖到右边了
return myWidth - handlerWidth;
}
return left;
}
return intitLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
if(dragType==TYPE_TOP){
if(top<-surPlusHeight){
return -surPlusHeight;
}else if(top>0){
return 0;
}
return top;
}
if(dragType==TYPE_BOTTOM){
if(top<myHeight-dragContentHeight){ //说明已经拖到顶部了
return myHeight-dragContentHeight;
}else if(top>myHeight - handlerHeight){ //说明已经拖到底部了
return myHeight - handlerHeight;
}
return top;
}
return initTop;
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
//没有这个方法拖动view里面的控件就不能点击了
@Override
public int getViewVerticalDragRange(View child) {
return myHeight;
}
@Override
public int getViewHorizontalDragRange(View child) {
return myHeight;
}
//打开
private void open(){
int finalLeft = 0;
int finalTop = 0;
if(dragType==TYPE_LEFT){
finalLeft = 0;
finalTop = initTop;
}
if(dragType==TYPE_RIGHT){
finalLeft = myWidth - dragContentWidth;
finalTop = initTop;
}
if(dragType==TYPE_TOP){
finalLeft = intitLeft;
finalTop = 0;
}
if(dragType==TYPE_BOTTOM){
finalLeft = intitLeft;
finalTop = myHeight - dragContentHeight;
}
if(myDrawerListener != null && getIsOpen() == false){
myDrawerListener.open();
}
isOpen = true;
viewDragHelper.smoothSlideViewTo(mDragContextView,finalLeft,finalTop);
invalidate();
}
//关闭
private void close(){
int finalLeft = 0;
int finalTop = 0;
if(dragType==TYPE_LEFT){
finalLeft = -surPlusWidth;
finalTop = initTop;
}
if(dragType==TYPE_RIGHT){
finalLeft = myWidth - handlerWidth;
finalTop = initTop;
}
if(dragType==TYPE_TOP){
finalLeft = intitLeft;
finalTop = -surPlusHeight;
}
if(dragType==TYPE_BOTTOM){
finalLeft = intitLeft;
finalTop = myHeight - handlerHeight;
}
if(myDrawerListener != null && getIsOpen() == true){
myDrawerListener.close();
}
isOpen = false;
viewDragHelper.smoothSlideViewTo(mDragContextView,finalLeft,finalTop);
invalidate();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
viewDragHelper.cancel();
return false;
}
boolean flag=viewDragHelper.shouldInterceptTouchEvent(event);
return flag;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
}
这里面最重要的两个方法为 onMeasure 和 onLayout
onMeasure
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
// 计算自定义的ViewGroup中所有子控件的大小
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 设置自定义的控件MyViewGroup的大小
setMeasuredDimension(measureWidth, measureHeight);
MarginLayoutParams paramsDrag = (MarginLayoutParams)mDragContextView.getLayoutParams();
final int contentWidthSpec = getChildMeasureSpec(widthMeasureSpec,paramsDrag.leftMargin+paramsDrag.rightMargin,paramsDrag.width);
final int contentHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsDrag.topMargin + paramsDrag.bottomMargin, paramsDrag.height);
mDragContextView.measure(contentWidthSpec,contentHeightSpec);
MarginLayoutParams paramsContent = (MarginLayoutParams)mContentView.getLayoutParams();
final int dragWidthSpec = getChildMeasureSpec(widthMeasureSpec, paramsContent.leftMargin + paramsContent.rightMargin, paramsContent.width);
final int dragHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsContent.topMargin + paramsContent.bottomMargin, paramsContent.height);
mContentView.measure(dragWidthSpec, dragHeightSpec);
getChildMeasureSpec必须计算好子view的大小,这里注意要算上子view的margin属性,不然你会发现子view的位置会有所偏差,因为我们默认只有两个子view所以就没有循环指定view的大小,简单粗暴。
mContentView和mDragContextView就是我们里面的两个子view。
接下来就是最最重要的onLayout方法了,指定所有子view的位置。
这时候view的宽度和高度必须算好然后设置,表达能力太差,可以移步洪洋大神的博客理解 >_>.
//计算坐标真烦
if(dragType==TYPE_LEFT){
if(isOpen){
intitLeft = cParams.leftMargin;
initRight = intitLeft + dragContentWidth;
}else{
intitLeft = -surPlusWidth;
initRight = intitLeft + dragContentWidth;
}
initTop = cParams.topMargin;
initBottom = initTop + dragContentHeight;
}else if(dragType==TYPE_RIGHT){
if(isOpen){
intitLeft = myWidth-dragContentWidth;
initRight = intitLeft + dragContentWidth;
}else{
intitLeft = myWidth-handlerWidth;
initRight = intitLeft + dragContentWidth;
}
initTop = cParams.topMargin;
initBottom = initTop + dragContentHeight;
}else if(dragType==TYPE_TOP){
if(isOpen){
initTop = cParams.topMargin;
initBottom = initTop+dragContentHeight;
}else{
initTop = -surPlusHeight;
initBottom = initTop + dragContentHeight;
}
intitLeft = cParams.leftMargin;
initRight = intitLeft + dragContentWidth;
}else if(dragType==TYPE_BOTTOM){
if(isOpen){
initTop = myHeight - dragContentHeight;
initBottom = initTop + dragContentHeight;
}else{
initTop = myHeight-handlerHeight;
initBottom = myHeight + surPlusHeight;
}
intitLeft = cParams.leftMargin;
initRight = intitLeft + dragContentWidth;
}
mDragContextView.layout(intitLeft, initTop, initRight, initBottom);
这一坨东西都是在计算坐标,因为你设置的方向不同,它的坐标就不同,还要考虑初始化的时候是否打开,这边算起来蛋疼。
onViewReleased是我们手指离开要调用的方法,这里我们要判断手指离开的时候坐标与(dragView-handler)/ 2.0f 至于这里判断的时getLeft()还是getTop()就要根据设置的方向来进行判断了 ,然后算出是否打开or关闭,滑动到指定位置我们用的是viewDragHelper.smoothSlideViewTo方法。
其它的就没什么可讲的了。
一定要记得layout方法的坐标位置一定要算对!
一定要记得layout方法的坐标位置一定要算对!
一定要记得layout方法的坐标位置一定要算对!
重要的事情说三遍,算的不对会出现不能显示or显示有偏差的情况。
(最后dragview的跟布局最好用linearLayout。我发现用relativeLayout作为跟布局并且为右边时候子布局的layout_marginRight竟然不显示,这貌似是系统的一个bug,我记得drawerlayout什么的都建议子布局为linearLayout)