前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2014-11-8Android学习------Android抽屉效果的实现案例--------动画Animation学习篇

2014-11-8Android学习------Android抽屉效果的实现案例--------动画Animation学习篇

作者头像
wust小吴
发布2022-03-07 14:23:43
1.4K0
发布2022-03-07 14:23:43
举报
文章被收录于专栏:风吹杨柳风吹杨柳

我学习Android都是结合源代码去学习,这样比较直观,非常清楚的看清效果,觉得很好,今天的学习源码是网上找的源码 百度搜就知道很多下载的地方 网上源码的名字叫:android抽屉效果.zip 我的博客写的比较乱,如果本篇文章没有看懂,

请先看上篇文章,地址分别为:

手势识别:http://blog.csdn.net/u014737138/article/details/40950431

动画监听:http://blog.csdn.net/u014737138/article/details/40952373

继承LinearLayout类需要重载的onLayout函数:http://blog.csdn.net/u014737138/article/details/40951087

继承LinearLayout类需要重载的onFinishInflate函数:http://blog.csdn.net/u014737138/article/details/40951985

首先我们需要对整个流程讲下,也就是实现的原理,就想我们讲PPT之前,先告诉观众,接下来要将的什么,有几点,按什么步奏?

第一:我们需要明确的是,我们实现上面的抽屉效果,就是必须先要定义个自己设计的布局,

我们实现的方法是去继承LinearLayout类

代码语言:javascript
复制
public class Panel extends LinearLayout {

继承这个类必须要做三件事:

1.构造函数需要重新写:

2.draw()函数必须重载:有两种函数onDraw(),和dispatchDraw(),后面这个函数一般是指没有背景图像的时候以及负责绘制子view

3.重载onLayout()函数:

4.重载onFinsihInflate()函数:

1.构造函数的实现:

代码语言:javascript
复制
	public Panel(Context context, AttributeSet attrs) {
		super(context, attrs);
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel);//从布局文件中加载属性到这个数组中
		mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); //动画的时间
		mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); //抽屉的位置
		mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false);
		mOpenedHandle = a.getDrawable(R.styleable.Panel_openedHandle);//打开背景
		mClosedHandle = a.getDrawable(R.styleable.Panel_closedHandle);//关闭背景
		a.recycle();//数组用完了记得要回收
		mOrientation = (mPosition == TOP || mPosition == BOTTOM) ? VERTICAL
				: HORIZONTAL;//方向,根据从布局文件中获得的位置,得出应该是垂直方向
		setOrientation(mOrientation);//然后让这个布局文件设置为该方向
		mState = State.READY;//当前抽屉状态为准备状态
		mGestureListener = new PanelOnGestureListener();//手势监听对象
		mGestureDetector = new GestureDetector(mGestureListener);//手势识别对象的初始化
		mGestureDetector.setIsLongpressEnabled(false);//手势识别不接受长按消息状态处理
	}

分析写这个里面的有些代码:

代码语言:javascript
复制
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel);

//从布局文件中加载属性到这个数组中

关于R.styleable.Panel这个知识点,前面有专题讲到,这里不再介绍,可以去看我面的文章

地址为:http://blog.csdn.net/u014737138/article/details/40789899

这里需要去关注这个这个XML文件里面到底是什么东西:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="Panel">

        <!-- Defines panel animation duration in ms. -->
        <attr name="animationDuration" format="integer" />
        <!-- Defines panel position on the screen. -->
        <attr name="position">

            <!-- Panel placed at top of the screen. -->
            <enum name="top" value="0" />
            <!-- Panel placed at bottom of the screen. -->
            <enum name="bottom" value="1" />
            <!-- Panel placed at left of the screen. -->
            <enum name="left" value="2" />
            <!-- Panel placed at right of the screen. -->
            <enum name="right" value="3" />
        </attr>
        <!-- Defines if flying gesture forces linear interpolator in animation. -->
        <attr name="linearFlying" format="boolean" />
        <!-- Defines opened handle (drawable/color). -->
        <attr name="openedHandle" format="reference|color" />
        <!-- Defines closed handle (drawable/color). -->
        <attr name="closedHandle" format="reference|color" />
    </declare-styleable>
    <declare-styleable name="SmoothButton">
        <attr name="transitionDrawable" format="reference" />
        <attr name="transitionDrawableLength" format="integer" />
        <attr name="transitionTextColorUp" format="color" />
        <attr name="transitionTextColorDown" format="color" />
    </declare-styleable>

</resources>

在attrs.xml文件中定义了两个<declare-styleable>标签:

1.<declare-styleable name="Panel">

2.<declare-styleable name="SmoothButton">

这两个都自动生成在R.styleable的类中, 需要注意的是一个知识点:TypedArray这个数组当用完了要记得随时回收

这个构造函数处理了几件事呢?

1.加载属性

2.确定布局的方向

3.确定抽屉的状态

4.手势识别事务的处理

这四件事我们放在后面说,这里先把必须要做的事说完,那就是当我们继承LinearLayout类的时候必须要重载的函数:

2.重载draw()函数:

代码语言:javascript
复制
	@Override
	protected void dispatchDraw(Canvas canvas) {
		// String name = getResources().getResourceEntryName(getId());
		// Log.d(TAG, name + " ispatchDraw " + mState);
		// this is why 'mState' was added:
		// avoid flicker before animation start
		if (mState == State.ABOUT_TO_ANIMATE && !mIsShrinking) {
			int delta = mOrientation == VERTICAL ? mContentHeight
					: mContentWidth;
			if (mPosition == LEFT || mPosition == TOP) {
				delta = -delta;
			}
			if (mOrientation == VERTICAL) {
				canvas.translate(0, delta);
			} else {
				canvas.translate(delta, 0);
			}
		}
		if (mState == State.TRACKING || mState == State.FLYING) {
			canvas.translate(mTrackX, mTrackY);
		}
		super.dispatchDraw(canvas);
	}

这个代码首先判断的就是状态:只有当抽屉准备好的时候才去绘制它,那什么时候交准备好呢?肯定是当前包含有抽屉的视图被用户打开了,也就是可见了,就叫准备好了,也即是你只有看到抽屉的时候,你才可以去打开它,如果你没有看到它,还谈打开吗?

变量mIsShrinking:代表的意思是:当前的组件是否是隐藏的,这里有这个变量的存在,主要是防止我们不知道这个组件的尺寸是多少,不知道显示给我们看的界面上这个组件是否可见,还有第二个原因就是这个抽屉是不是可见的

canvas.translate(0, delta);

//translate(float x,float y)函数是将整个canvas水平移动x,垂直移动y距离。可以通过translate函数来实现滚动的功能。

知道这个函数的功能我们再来看delta这个int类型的变量:

代码语言:javascript
复制
			int delta = mOrientation == VERTICAL ? mContentHeight
					: mContentWidth;
			if (mPosition == LEFT || mPosition == TOP) {
				delta = -delta;
			}

如果抽屉方向是垂直的话:delta为整个视图的高度

如果抽屉方向是水平的话:delta为整个视图的宽度

接下来就是在画布上如何显示这个抽屉了,也是按照方向来的:

代码语言:javascript
复制
			if (mOrientation == VERTICAL) {
				canvas.translate(0, delta);
			} else {
				canvas.translate(delta, 0);
			}

如果抽屉方向是垂直的话:画布水平移动0个像素,即水平方向不动;画布垂直方向移动视图的高度,即垂直方向跟界面同高

如果抽屉方向是水平的话:画布水平移动视图的宽度个像素,即水平方向跟界面同宽;垂直方向移动0个像素,即垂直方向不动

说到这里,就非常的清楚了抽屉的位置了。上面的都是初始化状态下抽屉的布局位置,如果我手指点击了按钮,也就是拉动了“环扣”

布局是需要重新画的,这个函数会重新加载哦,那么我们必须根据抽屉的状态去随时更新视图了:

代码语言:javascript
复制
		if (mState == State.TRACKING || mState == State.FLYING) {
			canvas.translate(mTrackX, mTrackY);
		}

当抽屉的状态是正在打开中,或者正在关闭中,这个过程画布像外面或者画布像里面缩放的过程,

用代码来处理就是画布的移动过程了:

至于这个时候的坐标就是就是手势识别中的监听事件去处理这个坐标了

2.重载onLayout()函数:

代码语言:javascript
复制
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		mContentWidth = mContent.getWidth();
		mContentHeight = mContent.getHeight();
	}

设置自定义布局,也就是抽屉的宽和高,从函数里面可以看到这个这个宽和高是参数 mConten来确定的,另外赋值给mContentWidth

那么我们需要关心这些变量的含义了,先放在这里,

3.重载onFinsihInflate()函数:

代码语言:javascript
复制
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		mHandle = findViewById(R.id.panelHandle);
		if (mHandle == null) {
			throw new RuntimeException(
					"Your Panel must have a View whose id attribute is 'R.id.panelHandle'");
		}
		mHandle.setOnTouchListener(touchListener);

		mContent = findViewById(R.id.panelContent);
		if (mContent == null) {
			throw new RuntimeException(
					"Your Panel must have a View whose id attribute is 'R.id.panelContent'");
		}

		// reposition children
		removeView(mHandle);
		removeView(mContent);
		if (mPosition == TOP || mPosition == LEFT) {
			addView(mContent);
			addView(mHandle);
		} else {
			addView(mHandle);
			addView(mContent);
		}

		if (mClosedHandle != null) {
			mHandle.setBackgroundDrawable(mClosedHandle);
		}
		mContent.setVisibility(GONE);
	}

这个函数的作用就是获得抽屉视图上控件的引用,

这里有两个Id,他们分别对应了两个组件,我们需要去看看这个id是怎么定义:

首先是在ids.xml中定义:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <item name="panelHandle" type="id"/>
    <item name="panelContent" type="id"/>

</resources>

2.然后在主布局文件中main.xml中需要用到这两个id才可以的:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:panel="http://schemas.android.com/apk/res/com.wust.slidingdrawfromleft"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#767c9b"
    android:orientation="vertical" >

    <com.wust.slidingdrawfromleft.activity.Panel
        android:id="@+id/leftPanel1"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_gravity="left"
        android:layout_weight="1"
        panel:closedHandle="@drawable/left_switcher_collapsed_background"
        panel:openedHandle="@drawable/left_switcher_expanded_background"
        panel:position="left" >

        <Button
            android:id="@id/panelHandle"
            android:layout_width="33dip"
            android:layout_height="fill_parent" />

        <LinearLayout
            android:id="@id/panelContent"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:orientation="vertical" >

            <Button
                android:id="@+id/btn"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:background="#999932"
                android:gravity="center"
                android:padding="18dip"
                android:text="This"
                android:textColor="#eee"
                android:textSize="16dip"
                android:textStyle="bold" />
        </LinearLayout>
    </com.wust.slidingdrawfromleft.activity.Panel>

</LinearLayout>

可以看到什么的两个控件用到了:

1.<Button android:id="@id/panelHandle" 代表点击抽屉的那个按钮,也即是像真实 的抽屉那个环扣

2.<LinearLayout android:id="@id/panelContent" 代表抽屉那个方框,也即是像真实的抽屉里面很大的面积的矩形,里面可以放很多东西

那么我们看看效果图上显示的这两个控件是什么呢?

也就是说我们清楚了两个变量的作用了:

1.mHandle :代表的是抽屉的“环扣” 也即是一个按钮,当用户点击了之后,它可以拉开“抽屉”,再次点击,它可以关闭“抽屉”

2.mContent:代表的是抽屉里面的矩形,也即是一个布局,程序员可以在里面加载自己的布局样式,可以放很多按钮,很多TextView等

接下来我们再来看看它做了什么处理:

“环扣”按钮找到了,立马监听它的监听事件:

mHandle.setOnTouchListener(touchListener);

然后是下面的重新布局:

removeView(mHandle); removeView(mContent);

先删除这个组件,通过设定的方向去重新加载这个组件:

if (mPosition == TOP || mPosition == LEFT) {//方向是TOP或者LEFT addView(mContent); addView(mHandle); } else {//方向是BOTTOM或者RIGHT addView(mHandle); addView(mContent); }

用户没有点击前 抽屉里面的东西你是不能给用户看的:

mContent.setVisibility(GONE);

同时必须要设置这个抽屉的背景图像

if (mClosedHandle != null) { mHandle.setBackgroundDrawable(mClosedHandle); }

做完了上面的构造和必须的重载我们就搞完了整个布局的实现,也就是继承LinearLayout,至少目前我们能够让这个效果显示出来,

现在要处理的就是关于控件的监听事件的处理,鼠标的计算,等等

这个时候我们首先回到Draw()函数:里面有个代码就是按钮“环扣”的监听事件:

mHandle.setOnTouchListener(touchListener);//上面已经 说了这个mHandle对象代表的就是环扣那个按钮,用户点击可以关闭和打开抽屉

参数是:touchListener,关于按钮的监听事件我这里就不再赘述了,初学者可以看我前面的文章

地址:http://blog.csdn.net/u014737138/article/details/40478027

这里要看的是本案例中的做法:它主要是监听抽屉是被打开还是被关闭

代码语言:javascript
复制
	OnTouchListener touchListener = new OnTouchListener() {
		int initX;
		int initY;
		boolean setInitialPosition;

		public boolean onTouch(View v, MotionEvent event) {
			// Log.d(TAG, "state: " + mState + " x: " + event.getX() + " y: " +
			// event.getY());
			int action = event.getAction();
			if (action == MotionEvent.ACTION_DOWN) {
				initX = 0;
				initY = 0;
				if (mContent.getVisibility() == GONE) {
					// since we may not know content dimensions we use factors
					// here
					if (mOrientation == VERTICAL) {
						initY = mPosition == TOP ? -1 : 1;
					} else {
						initX = mPosition == LEFT ? -1 : 1;
					}
				}
				setInitialPosition = true;
			} else {
				if (setInitialPosition) {
					// now we know content dimensions, so we multiply factors...
					initX *= mContentWidth;
					initY *= mContentHeight;
					// ... and set initial panel's position
					mGestureListener.setScroll(initX, initY);
					setInitialPosition = false;
					// for offsetLocation we have to invert values
					initX = -initX;
					initY = -initY;
				}
				// offset every ACTION_MOVE & ACTION_UP event
				event.offsetLocation(initX, initY);
			}
			if (!mGestureDetector.onTouchEvent(event)) {
				if (action == MotionEvent.ACTION_UP) {
					// tup up after scrolling
					post(startAnimation);
				}
			}
			return false;
		}
	};

手势识别的OnTouchListener的事件处理: 我们可以通过MotionEvent的getAction()方法来获取Touch事件的类型,包括 ACTION_DOWN(按下触摸屏), ACTION_MOVE(按下触摸屏后移动受力点), ACTION_UP(松开触摸屏)

这个函数的变量:boolean setInitialPosition;//方向 如果我们点击了,这个变量代表是拉开抽屉,我们再次点击,这个变量就是代表关闭

代码语言:javascript
复制
				if (mContent.getVisibility() == GONE) {
					// since we may not know content dimensions we use factors
					// here
					if (mOrientation == VERTICAL) {
						initY = mPosition == TOP ? -1 : 1;
					} else {
						initX = mPosition == LEFT ? -1 : 1;
					}
				}

//如果当前的视图不可见,主要担心的事就是:当前响应的这个控件的尺寸有多大我们是不知道的。需要做一个判断

当这个抽屉看不到的时候,我们需要将抽屉画布的x,y坐标设置为(-1,-1)

setInitialPosition = true;//如果手势是按下,方向得到确定了,置为true 关闭抽屉:

代码语言:javascript
复制
				if (setInitialPosition) {//方向确定了
					// now we know content dimensions, so we multiply factors...
					initX *= mContentWidth;//初始化当前响应控件的大小:宽和高
					initY *= mContentHeight;
					// ... and set initial panel's position  //这里设置面板的位置
					mGestureListener.setScroll(initX, initY);//手势识别事件的触发,设置手势滑动的范围
					setInitialPosition = false;//然后将方向设置为false
					// for offsetLocation we have to invert values
					initX = -initX;
					initY = -initY;
				}
				// offset every ACTION_MOVE & ACTION_UP event
				event.offsetLocation(initX, initY);

这个处理对应的是:ACTION_MOVE & ACTION_UP event

event.offsetLocation视图进行偏移还原,以便下次使用

我们拉开了“环扣”是需要有业务处理的,就是打开抽屉,这个交给下面的代码来处理:

代码语言:javascript
复制
			if (!mGestureDetector.onTouchEvent(event)) {//手势识别捕捉手势类型
				if (action == MotionEvent.ACTION_UP) {//松开触摸屏)
					// tup up after scrolling
					post(startAnimation);
				}
			}

函数post(startAnimation);从参数里面我们可以这是一个动画,为什么要用动画呢,我们还是用效果来说说:

未打开

打开这个过程

画布是一个慢慢移动的过程,这里存在一个一个动画的过程:

这个函数是一个View类的一个函数,将调用线程去执行,先不管这个函数,我们去看看动画,我们是怎么做的:

代码语言:javascript
复制
	Runnable startAnimation = new Runnable() {
		public void run() {
			// this is why we post this Runnable couple of lines above:
			// now its save to use mContent.getHeight() && mContent.getWidth()
			TranslateAnimation animation;
			int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;
			if (mState == State.FLYING) {
				mIsShrinking = (mPosition == TOP || mPosition == LEFT)
						^ (mVelocity > 0);
			}
			int calculatedDuration;
			if (mOrientation == VERTICAL) {
				int height = mContentHeight;
				if (!mIsShrinking) {
					fromYDelta = mPosition == TOP ? -height : height;
				} else {
					toYDelta = mPosition == TOP ? -height : height;
				}
				if (mState == State.TRACKING) {
					if (Math.abs(mTrackY - fromYDelta) < Math.abs(mTrackY
							- toYDelta)) {
						mIsShrinking = !mIsShrinking;
						toYDelta = fromYDelta;
					}
					fromYDelta = (int) mTrackY;
				} else if (mState == State.FLYING) {
					fromYDelta = (int) mTrackY;
				}
				// for FLYING events we calculate animation duration based on
				// flying velocity
				// also for very high velocity make sure duration >= 20 ms
				if (mState == State.FLYING && mLinearFlying) {
					calculatedDuration = (int) (1000 * Math
							.abs((toYDelta - fromYDelta) / mVelocity));
					calculatedDuration = Math.max(calculatedDuration, 20);
				} else {
					calculatedDuration = mDuration
							* Math.abs(toYDelta - fromYDelta) / mContentHeight;
				}
			} else {
				int width = mContentWidth;
				if (!mIsShrinking) {
					fromXDelta = mPosition == LEFT ? -width : width;
				} else {
					toXDelta = mPosition == LEFT ? -width : width;
				}
				if (mState == State.TRACKING) {
					if (Math.abs(mTrackX - fromXDelta) < Math.abs(mTrackX
							- toXDelta)) {
						mIsShrinking = !mIsShrinking;
						toXDelta = fromXDelta;
					}
					fromXDelta = (int) mTrackX;
				} else if (mState == State.FLYING) {
					fromXDelta = (int) mTrackX;
				}
				// for FLYING events we calculate animation duration based on
				// flying velocity
				// also for very high velocity make sure duration >= 20 ms
				if (mState == State.FLYING && mLinearFlying) {
					calculatedDuration = (int) (1000 * Math
							.abs((toXDelta - fromXDelta) / mVelocity));
					calculatedDuration = Math.max(calculatedDuration, 20);
				} else {
					calculatedDuration = mDuration
							* Math.abs(toXDelta - fromXDelta) / mContentWidth;
				}
			}

			mTrackX = mTrackY = 0;
			if (calculatedDuration == 0) {
				mState = State.READY;
				if (mIsShrinking) {
					mContent.setVisibility(GONE);
				}
				postProcess();
				return;
			}

			animation = new TranslateAnimation(fromXDelta, toXDelta,fromYDelta, toYDelta);
			animation.setDuration(calculatedDuration);
			animation.setAnimationListener(animationListener);
			if (mState == State.FLYING && mLinearFlying) {
				animation.setInterpolator(new LinearInterpolator());
			} else if (mInterpolator != null) {
				animation.setInterpolator((android.view.animation.Interpolator) mInterpolator);
			}
			startAnimation(animation);
		}
	};

我们要关注四个变量:

int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;

这四个变量是代表什么呢,?我们上面定义了一个移动动画对象,TranslateAnimation 构造的是需要这四个参数的:

前面的文章有介绍:地址:http://blog.csdn.net/u014737138/article/details/40861929

代码语言:javascript
复制
fromXDelta  为动画起始时 X坐标上的位置  
toXDelta    为动画结束时 X坐标上的位置    
fromYDelta  为动画起始时 Y坐标上的位置  
toYDelta    为动画结束时 Y坐标上的位置  

动画起始坐标到结束坐标,也就是我们的很多代码都是计算动画的坐标的。

代码语言:javascript
复制
	if (mState == State.FLYING) {
				mIsShrinking = (mPosition == TOP || mPosition == LEFT)
						^ (mVelocity > 0);
			}

如果状态正在移动中,我们通过速度来确定是否是隐藏,mIsShrinking是一个Boolean变量 int calculatedDuration;//动画的时长,也就是拉开抽屉画多久,两个图片变化完成的整个过程

垂直方向:时长

代码语言:javascript
复制
				if (mState == State.FLYING && mLinearFlying) {
					calculatedDuration = (int) (1000 * Math
							.abs((toYDelta - fromYDelta) / mVelocity));
					calculatedDuration = Math.max(calculatedDuration, 20);
				} else {
					calculatedDuration = mDuration
							* Math.abs(toYDelta - fromYDelta) / mContentHeight;
				}

距离/速度=时间,相信这段代码都看的懂了, 垂直方向:坐标计算:

代码语言:javascript
复制
				int height = mContentHeight;
				if (!mIsShrinking) {
					fromYDelta = mPosition == TOP ? -height : height;
				} else {
					toYDelta = mPosition == TOP ? -height : height;
				}
				if (mState == State.TRACKING) {
					if (Math.abs(mTrackY - fromYDelta) < Math.abs(mTrackY
							- toYDelta)) {
						mIsShrinking = !mIsShrinking;
						toYDelta = fromYDelta;
					}
					fromYDelta = (int) mTrackY;
				} else if (mState == State.FLYING) {
					fromYDelta = (int) mTrackY;
				}

坐标的捕捉:

代码语言:javascript
复制
private PanelOnGestureListener mGestureListener;//手势监听对象

它的定义:
	class PanelOnGestureListener implements OnGestureListener {//这个类是实现手势监听器的,那么就需要去重载必须的函数
		float scrollY;//滚动的坐标Y
		float scrollX;//滚动的坐标X

		public void setScroll(int initScrollX, int initScrollY) {//设置函数
			scrollX = initScrollX;
			scrollY = initScrollY;
		}

		public boolean onDown(MotionEvent e) {//手势按下  // 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发 
			scrollX = scrollY = 0;
			if (mState != State.READY) {//目前的状态没有准备好,即将开始动画的操作
				// we are animating or just about to animate
				return false;
			}
			mState = State.ABOUT_TO_ANIMATE;//状态改变为正在准备放置动画
			mIsShrinking = mContent.getVisibility() == VISIBLE;//屏幕当前可看的话,返回true,否则返回false,也就是在进入有抽屉效果的界面上,我们才可以进行抽屉的效果展示
			if (!mIsShrinking) {
				// this could make flicker so we test mState in dispatchDraw()
				// to see if is equal to ABOUT_TO_ANIMATE
				mContent.setVisibility(VISIBLE);//如果当前的屏幕不可见,我们设置它为可见
			}
			return true;
		}
		// 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {//滑动处理函数
			mState = State.FLYING;//状态是正在滑动
			mVelocity = mOrientation == VERTICAL ? velocityY : velocityX;//速度的设置看方向是Y方向的还是X方向的,如果是垂直方向,就是Y方向,如果不是垂直就是水平,那么就是X方向
			post(startAnimation);//执行动画
			return true;
		}
		// 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发  
		public void onLongPress(MotionEvent e) {//手势是长按,不用处理
			// not used
		}
		
		// 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {//滚动
			mState = State.TRACKING;//手势是在移动中的
			float tmpY = 0, tmpX = 0;//临时变量
			if (mOrientation == VERTICAL) {//是垂直方向的滚动
				scrollY -= distanceY;//滚动的坐标Y是时刻在变化的
				if (mPosition == TOP) {//如果现在的位置在顶部
					tmpY = ensureRange(scrollY, -mContentHeight, 0);//规定Y可以移动的范围
				} else {//方向是底部
					tmpY = ensureRange(scrollY, 0, mContentHeight);
				}
			} else {//如果是水平方向
				scrollX -= distanceX;//就是用X方向的坐标来参考
				if (mPosition == LEFT) {//方向是左边
					tmpX = ensureRange(scrollX, -mContentWidth, 0);
				} else {//方向是右部
					tmpX = ensureRange(scrollX, 0, mContentWidth);
				}
			}
			// 当X 和 Y 方向的值都确定了,那么就需要去看这个值是否等于等于你手指移动的坐标,也就是设置手势坐标
			if (tmpX != mTrackX || tmpY != mTrackY) {
				mTrackX = tmpX;//设置手指移动中的X坐标
				mTrackY = tmpY;//设置手指移动中的Y坐标
				invalidate();//更新视图
			}
			return true;
		}

		/*  
		* 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发  
		* 注意和onDown()的区别,强调的是没有松开或者拖动的状态  
		*/  
		public void onShowPress(MotionEvent e) {
			// not used
		}
		// 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发   
		public boolean onSingleTapUp(MotionEvent e) {
			// simple tap: click
			post(startAnimation);
			return true;
		}
	}

关于GestureListener请看我上面的文章

坐标计算好之后我们便开始动画:

代码语言:javascript
复制
			animation = new TranslateAnimation(fromXDelta, toXDelta,fromYDelta, toYDelta);
			animation.setDuration(calculatedDuration);
			animation.setAnimationListener(animationListener);
			if (mState == State.FLYING && mLinearFlying) {
				animation.setInterpolator(new LinearInterpolator());
			} else if (mInterpolator != null) {
				animation.setInterpolator((android.view.animation.Interpolator) mInterpolator);
			}
			startAnimation(animation);

这里需要去学习动画的监听事件,请看前面的文章

动画的监听事件处理:

代码语言:javascript
复制
	private AnimationListener animationListener = new AnimationListener() {
		public void onAnimationEnd(Animation animation) {//动画结束
			mState = State.READY;
			if (mIsShrinking) {
				mContent.setVisibility(GONE);
			}
			postProcess();
		}

		public void onAnimationRepeat(Animation animation) {//动画重新开始
		}

		public void onAnimationStart(Animation animation) {//动画开始
			mState = State.ANIMATING;
		}
	};

图片的改变是:

未打开

打开 postProcess():

代码语言:javascript
复制
	private void postProcess() {
		if (mIsShrinking && mClosedHandle != null) {
			mHandle.setBackgroundDrawable(mClosedHandle);
		} else if (!mIsShrinking && mOpenedHandle != null) {
			mHandle.setBackgroundDrawable(mOpenedHandle);
		}
		// invoke listener if any
		if (panelListener != null) {
			if (mIsShrinking) {
				panelListener.onPanelClosed(Panel.this);
			} else {
				panelListener.onPanelOpened(Panel.this);
			}
		}
	}

关于panelListener变量,是自定义视图的一个回调函数接口类型:

代码语言:javascript
复制
	/**
	 * Callback invoked when the panel is opened/closed.
	 */
	public static interface OnPanelListener {
		/**
		 * Invoked when the panel becomes fully closed.
		 */
		public void onPanelClosed(Panel panel);


		/**
		 * Invoked when the panel becomes fully opened.
		 */
		public void onPanelOpened(Panel panel);
	}

整个程序需要的变量定义:

代码语言:javascript
复制
	private boolean mIsShrinking;//收缩 也就是隐藏抽屉
	private int mPosition;//位置,也就是抽屉的位置,左,右,上,下
	private int mDuration;//打开抽屉和关闭抽屉需要的时间
	private boolean mLinearFlying;
	private View mHandle;//视图上的子组件按钮,也就是那个“环扣”
	private View mContent;//代表抽屉的矩形
	private Drawable mOpenedHandle;//显示抽屉背景
	private Drawable mClosedHandle;//关闭抽屉背景
	private float mTrackX;//手势拖动的x坐标
	private float mTrackY;
	private float mVelocity;//速度

	private OnPanelListener panelListener;//回调函数监听对象

	public static final int TOP = 0;
	public static final int BOTTOM = 1;
	public static final int LEFT = 2;
	public static final int RIGHT = 3;

	private enum State {
		ABOUT_TO_ANIMATE, ANIMATING, READY, TRACKING, FLYING,
	};

	private State mState;//对应上面的四个动态
	private Interpolator mInterpolator;//内插器
	private GestureDetector mGestureDetector;//手势识别
	private int mContentHeight;//抽屉的高度
	private int mContentWidth;//抽屉的宽度
	private int mOrientation;//方向
	private PanelOnGestureListener mGestureListener;//手势监听对象

就这样了,太长了,写了好久,还没有理清,写文章的能力有待提高,就这样看吧 估计讲的不清楚,有的还搞错了都有,有错误请指出,学习中

源码地址:http://download.csdn.net/detail/u014737138/8139417

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档