Launcher的托盘 ——SliderView研究

本文原创http://blog.csdn.net/yanbin1079415046,转载请注明出处。

  今天,我们来看一看ADW_Launcher的托盘。对于使用托盘这个名称,我其实也不确定使用的是否准确。但是根据ADW_Launcher中将最底部的那个方格(或者主页)图片名称称为tray_expend和tray_collapse可以看出,这个位置应该就是托盘。况且这也只是一个名字而已,我们就暂且这样叫吧。为了更形象的说明我们将要讲解的是什么东西,下面就给出这部分的图,如下:

   这是ADW_Launcher主页中最底部不会随着屏幕滑动而滑动的部分。这部分在ADW_Launcher主页面布局文件launcher.xml中的对应为:LinearLayout,其id为drawer_toolbar。具体请看我的另一篇文章ADW_Launcher主页面布局文件(launcher.xml)浅析

我们知道上图中ImageView(LinearLayout中,中间)部分的位置是被SliderView占据的,它就是我们今天要讲的内容。SliderView继承自ImageView,且重写了onTouchEvent()方法来实现对滑动事件的处理。

  我们知道,当我们点击SliderView的时候,格子图片变成主页图片,当我们将SliderView往上拖动到一定的距离的时候,整个LinearLayout以及左右两边的圆点都会消失,此时在这一区域显示的控件为DockBar,也就是MiniLauncher。因此,SliderView的作用就是桌面与所有应用程序界面的切换,以及MiniLauncher的展示。DockBar展示之后,如果要回到普通的桌面,可以在DockBar的位置往下拖动即可。关于拖动之后进入MiniLauncher的效果图如下:

Launcher的托盘 ——SliderView研究_第1张图片

  在说明拖动之后进入MiniLauncher 这个效果出现的代码流程之前,先说明两点内容:

  一:SliderView往上拖动一定距离后消失的原理。

  在托盘的上方某一个位置放置 "一列一直在滑动的箭头",此为一个矩形区域,在SliderView中用mTargetRect来表示,它是一个Rect。SliderView每次移动后都会产生一个Rect,名为mTmpRect。当mTmpRect的顶部与mTargetRect的底部产生交叉时(调用这句代码:mTargetRect.intersect(mTmpRect)并且返回的是true值),LinearLayout就会消失了,进入到DockBar。当然还有一个mLimitRect来控制SliderView可以滑动的区域。更具体的内容我们将在下面的源代码中进行说明。

  二:Launcher.java中字段名称与launcher.xml中控件的对应:

  mHandleView : 代表的是launcher.xml中的SliderView。也是我们今天的重点

  drwToolbar : 代表的是launcher.xml中的LinearLayout。

  mPreviousView,mNextView : 代表的是launcher.xml中的底部左边和右边的圆形指示点。

  mMiniLauncher : 代表的是launcher.xml中DockBar内部的MiniLauncher。

  下面就说一说出现上述效果的代码执行流程(用户点击SliderView的效果执行流程也一起给出):

1、用户点击SliderView

  在SliderView中经过一系列的判断之后,最终会交给SliderView的dispatchClickedEvent()方法进行处理。代码如下:

 /////代码一
 public boolean onTouchEvent(MotionEvent event) {
	final int action = event.getAction();
	final float x = event.getX();
	final float y = event.getY();
	switch (action) {
		case MotionEvent.ACTION_DOWN: {
			//...
			break;
		}
		case MotionEvent.ACTION_MOVE:
			//...
			break;
		case MotionEvent.ACTION_UP:
			Log.i(LOG_TAG, "ACTION_UP");
			final long upTime=System.currentTimeMillis();
			final boolean shortTap=((upTime-mTouchTime)<350);
			//只是很短时间的点击或者是长时间点击但是未移动时执行
			if((!mSliding && mSlidingEnabled) ||(shortTap&&!mTriggered)){
				dispatchClickedEvent();
			}
		case MotionEvent.ACTION_CANCEL:
			\\...
			break;
	}
}		

接着程序将执行OnTriggerListener的onClick()回调方法。代码如下:

////代码二
 private void dispatchClickedEvent() {
	if (mOnTriggerListener != null) {
		mOnTriggerListener.onClick(this);
	}
}

OnTriggerListener接口定义如下:

////代码三
/**
 * 在Launcher.java中注册该监听,执行回调。
 */
public interface OnTriggerListener {
        public static final int UP=1;
        public static final int DOWN=2;
        public static final int LEFT=4;
        public static final int RIGHT=8;
		//滑动处理,回调该方法表示可以隐藏SliderView了
        void onTrigger(View v, int whichHandle);
        void onGrabbedStateChange(View v, boolean grabbedState);
		//SliderView点击事件回调
        void onClick(View v);
    }

此时程序将进入到Launcher.java中执行。执行setupViews()中的mHandleView的回调方法。其代码如下:

////代码四
private void setupViews() {
	//...
	mHandleView = (SliderView) dragLayer.findViewById(R.id.all_apps);
	//SliderView方法回调
	mHandleView.setOnTriggerListener(new OnTriggerListener() {
		public void onTrigger(View v, int whichHandle) {
			//...
		}

		public void onGrabbedStateChange(View v, boolean grabbedState) {
		}

		public void onClick(View v) {
			if (allAppsOpen) {
				closeAllApps(true);
			} else {
				showAllApps(true, null);
			}
		}
	});
	//...
}

  allAppsOpen的初始值为false。如果当前SliderView的背景图为格子,此时点击SliderView程序将执行showAllApps方法,此时将进入到展示所有的应用程序的界面。showAllApps的主要作用是隐藏壁纸和底部的左右指示圆点以及展示存放了所有应用程序的Drawer。这部分内容请自己参见源码,closeAllApps方法也就不再赘述。

  2、用户拖动SliderView使得LinearLayout隐藏。

  如果用户拖动SliderView满足一定的条件(上面已经阐述),那么程序此时将执行隐藏LinearLayout,显示MiniLauncher的操作。在SliderView中的代码如下(代码中很多剔除的部分,我们这里只抽取主干):

/////代码五
public boolean onTouchEvent(MotionEvent event) {
	//...
	switch (action) {
		case MotionEvent.ACTION_DOWN: {
		//...
			break;
		}
		case MotionEvent.ACTION_MOVE:
//            	Log.i(LOG_TAG, "ACTION_MOVE");
			if(mTracking){
				//...
				if(mOrientation!=REST){
					//...
					for(ImageView v:mTargets){
						v.getHitRect(mTargetRect);
						//如果两个矩形有交叉
						if(mTargetRect.intersect(mTmpRect)){
							thresholdReached=true;
							targetReached=(Integer)v.getTag();
						}
					}
					if (!mTriggered && thresholdReached) {
						//...
						dispatchTriggerEvent(targetReached);
						//....
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			//...
		case MotionEvent.ACTION_CANCEL:
			//...
			break;
	}
}	

  到这里就该执行dispatchTriggerEvent()方法了,它将去回调OnTriggerListener的onTrigger()方法,此时程序将回到Launcher.java中继续执行。dispatchTriggerEvent()方法如下:

//// 代码6
private void dispatchTriggerEvent(int whichHandle) {
	performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
			HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
	if (mOnTriggerListener != null) {
		mOnTriggerListener.onTrigger(this, whichHandle);
	}
}

  程序将执行setupViews()中的mHandleView的回调方法。该方法如下:

////代码七
private void setupViews() {
	//...
	mHandleView = (SliderView) dragLayer.findViewById(R.id.all_apps);
	//SliderView方法回调
	mHandleView.setOnTriggerListener(new OnTriggerListener() {
		public void onTrigger(View v, int whichHandle) {
			mDockBar.open();
		}

		public void onGrabbedStateChange(View v, boolean grabbedState) {
		}

		public void onClick(View v) {
			//...
		}
	});
	//...
	mDockBar = (DockBar) findViewById(R.id.dockbar);
		mDockBar.setDockBarListener(new DockBarListener() {
			public void onOpen() {
				mHandleView.setVisibility(View.GONE);
				drwToolbar.setVisibility(View.GONE);
				if (mNextView.getVisibility() == View.VISIBLE) {
					mNextView.setVisibility(View.INVISIBLE);
					mPreviousView.setVisibility(View.INVISIBLE);
				}
			}
			public void onClose() {
				mHandleView.setVisibility(View.VISIBLE);
				drwToolbar.setVisibility(View.VISIBLE);
				if (showDots && !isAllAppsVisible()) {
					mNextView.setVisibility(View.VISIBLE);
					mPreviousView.setVisibility(View.VISIBLE);
				}

			}
		});
}
    

从代码中可以看出,程序接下来将去执行DockBar的open()方法。在open方法中,上面代码中的DockBarListener的回调方法onOpen()将会被执行,从而实现mHandleView、drwToolbar、mNextView和mPreviousView的隐藏。对于这些字段的含义,上面已经说明。

DockBarListener接口的定义如下,它将监听DockBar的onOpen()和onClose()事件。

////代码八
/**
 * Interface definition for a callback to be invoked when a tab is triggered
 * by moving it beyond a threshold.
 */
public interface DockBarListener {
	void onOpen();
	void onClose();
}


  DockBar的open()方法如下:它的作用是开一个动画并且使用该动画显示DockBar。

////代码九
public void open(){
	dispatchDockBarEvent(true);//执行DockBarListener的回调方法
	setClickable(false);
	mState=OPEN;
	setVisibility(View.VISIBLE);//让dockBar可见
	int x=0;
	int y=0;
	//mPosition代码DockBar所处的位置,此时为  BOTTOM,即底部 
	switch (mPosition) {
		case LEFT:
			x=getWidth();
			break;
		case RIGHT:
			x=getWidth();
			break;
		case TOP:
			y=-getHeight();
			break;
		case BOTTOM:
			y=getHeight();
			break;
	}
	TranslateAnimation anim=new TranslateAnimation(x, 0,y, 0);
	anim.setDuration(ANIM_DURATION);
	startAnimation(anim);
}


  dispatchDockBarEvent()方法如下,它的作用是执行DockBarListener的回调,由于此时open,即打开DockBar,那么onOpen()方法将会被回调。

////代码十
 /**
 * Dispatches a trigger event to listener. Ignored if a listener is not set.
 * @param whichHandle the handle that triggered the event.
 */
private void dispatchDockBarEvent(boolean open) {
	if (mDockBarListener != null) {
		if(open){
			mDockBarListener.onOpen();
		}else{
			mDockBarListener.onClose();
		}
	}
}


  紧接着,代码七中的onOpen()方法将会被执行,这一过程上面已经说过。到这里,用户拖动SliderView而进入MiniLauncher的过程就分析完毕。

  整理一下,上面我们讲的内容主要有两点:SliderView的点击事件处理流程和SliderView拖动达到显示DockBar的要求之后的处理的流程。上面已经说过,SliderView的滑动事件在SliderView这个类中已经做了处理,现在,我们就进入到SliderView中去了解一下这个滑动的过程。

  SliderView滑动过程分析:

  由于在Launcher页面启动之后,SliderView的图片会被设置成格子,也就说,SliderView的onLayout()方法会被执行。下面就从SliderView的初始化和onLayout()说起。

  一:构建SliderView的过程 (init()和onLayout()方法)

   构造方法中将设置默认的滑动方向(mSlideDirections,默认为竖直方向)以及当前设备下的可滑动距离(mTargetDistance)。init方法初始化三个Rect,并且将当前view距离父控件上下左右的值设置为临时矩形区域的坐标。onLayout方法中会设置临时矩形区域和可滑动区域(第一次用临时矩形区域的值)的值,还有就是记录下当前SliderView的初始位置。

////代码十一
public SliderView(Context context, AttributeSet attrs, int defStyle) {
	super(context, attrs, defStyle);
	TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SliderView,
			defStyle, 0);
	//从TypeArray中取出index为R.styleable.SliderView_slideDirections的值,为1
	//private static final int VERTICAL = 1;
	mSlideDirections = a.getInt(
		R.styleable.SliderView_slideDirections, mSlideDirections);
	//根据当前设备的DisplayMetrics取得对应的目标距离(可滑动距离)的像素值
	mTargetDistance = a.getDimensionPixelSize(
			R.styleable.SliderView_targetDistance, mTargetDistance);
	Log.i(LOG_TAG, "mSlideDirections==" + mSlideDirections + ",mTargetDistance==" + mTargetDistance);
	init();
}
private void init(){
	//mSlideDirections=OnTriggerListener.UP|OnTriggerListener.DOWN;
	mLimitRect=new Rect();//可移动的矩形区域
	mTmpRect = new Rect();//每次滑动产生的矩形区域
	mTargetRect=new Rect();//目标矩形区域(就是滑动箭头的区域)
	//Hit rectangle in parent's coordinates
	//作用是将当前view的mLeft(到父控件左边的距离,其他类似), mTop, mRight, mBottom  四个值设置给
	//mTmpRect的四个坐标
	getHitRect(mTmpRect);
	Log.i(LOG_TAG,"init--mTmpRect==(" + mTmpRect.left + "," + mTmpRect.top + "," + mTmpRect.right + "," + mTmpRect.bottom +")");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
		int bottom) {
	if(!mAnimating && !mTracking){
		super.onLayout(changed, left, top, right, bottom);
		getHitRect(mTmpRect);
		Log.i(LOG_TAG, "onLayout--mTmpRect==(" + mTmpRect.left + "," + mTmpRect.top + "," + mTmpRect.right + "," + mTmpRect.bottom +")");
		initPosition=new Point(getLeft(), getTop());
		//将mTmpRect的坐标设置给mLimitRect
		mLimitRect.set(mTmpRect);
	}
}


  二:滑动事件处理

  为了方便讲解,将onTouchEvent()的代码贴出来。

////代码十二
public boolean onTouchEvent(MotionEvent event) {
	final int action = event.getAction();
	final float x = event.getX();
	final float y = event.getY();
	switch (action) {
		case MotionEvent.ACTION_DOWN: {
			Log.i(LOG_TAG, "ACTION_DOWN");
			if(mSlidingEnabled){
				mTracking = true;
				mTriggered = false;
				//在launcher中并未对该回调方法进行处理,因此不用管,作用是告诉Launcher,SliderView的事件发生了变化
				setGrabbedState(true);
				mPreviousX=(int) x;//记录按下时的x位置
				mPreviousY=(int) y;//记录按下时的y位置
				showTarget();
			}
			setState(STATE_PRESSED);//设置当前是press的状态
			mTouchTime=System.currentTimeMillis();
			break;
		}
		case MotionEvent.ACTION_MOVE:
//            	Log.i(LOG_TAG, "ACTION_MOVE");
			if(mTracking){
				if(mOrientation==REST){
					if((((mSlideDirections&OnTriggerListener.UP)==OnTriggerListener.UP)||
							((mSlideDirections&OnTriggerListener.DOWN)==OnTriggerListener.DOWN))  && (Math.abs(mPreviousX-x)*2<Math.abs(mPreviousY-y)) ){
						mOrientation=VERTICAL;
					}else if((((mSlideDirections&OnTriggerListener.LEFT)==OnTriggerListener.LEFT)||
							((mSlideDirections&OnTriggerListener.RIGHT)==OnTriggerListener.RIGHT)) && (Math.abs(mPreviousX-x)>Math.abs(mPreviousY-y)*2)){
						mOrientation=HORIZONTAL;
					}
				}
				if(mOrientation!=REST){
					mSliding=true;
					moveHandle(x, y);
					//移动后实时的 Rect信息被放在mTmpRect中
					getHitRect(mTmpRect);
					Log.i(LOG_TAG,"action_move--mTmpRect==(" + mTmpRect.left + "," + mTmpRect.top + "," + mTmpRect.right + "," + mTmpRect.bottom +")");
					boolean thresholdReached=false;
					int targetReached=-1;
					for(ImageView v:mTargets){
						v.getHitRect(mTargetRect);
						//预计  (215,663,265,673)
						Log.i(LOG_TAG,"action_move--mTargetRect==(" + mTargetRect.left + "," + mTargetRect.top + "," + mTargetRect.right + "," + mTargetRect.bottom +")");
						//如果两个矩形有交叉
						if(mTargetRect.intersect(mTmpRect)){
							thresholdReached=true;
							targetReached=(Integer)v.getTag();
						}
					}
					if (!mTriggered && thresholdReached) {
						mTriggered = true;
						mTracking = false;
						setState(STATE_ACTIVE);
						dispatchTriggerEvent(targetReached);
						hideTarget();
						reset(true);
						setGrabbedState(false);
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			Log.i(LOG_TAG, "ACTION_UP");
			final long upTime=System.currentTimeMillis();
			final boolean shortTap=((upTime-mTouchTime)<350);
			//只是很短时间的点击或者是长时间点击但是未移动时执行
			if((!mSliding && mSlidingEnabled) ||(shortTap&&!mTriggered)){
				dispatchClickedEvent();
			}
		case MotionEvent.ACTION_CANCEL:
			
			//取消时动画执行流程:
			//1、reset(true)----if条件成立,开始动画,
			//2、动画结束后调用  onAnimationDone() 方法
			//3、onAnimationDone()中调用reset(false)进入else,使得sliderview复位。
			Log.i(LOG_TAG, "ACTION_CANCEL");
			//将所有参数还原
			mTracking = false;
			mTriggered = false;
			mSliding=false;
			mOrientation=REST;
			reset(true);
			hideTarget();
			setGrabbedState(false);
			break;
	}
//        Log.i(LOG_TAG, "----ACTION_END----mTracking==" + mTracking + ",super.onTouchEvent(event)==" + super.onTouchEvent(event));
	return mTracking || super.onTouchEvent(event);
}


A、点击或者拖动SliderView时ACTION_DOWN的处理。

  作用:记录下按下的时间,用来与后面的up事件比对来判断是否是点击操作,showTarget()方法的作用是显示SliderView上方滑动的箭头。setState()的作用是设置动作方式(比如Press)。注意mSlidingEnabled的值可以在launcher中进行设置,当然也可以在 设置的系统设置中进行设置,初始值为true。

  showTarget()方法的代码如下:它首先回去判断显示向上滑动的箭头的ArrayList是否存在,不存在则创建,存在则更行。

////代码十三
/**
 * 显示向上滑动的箭头
 */
void showTarget() {
	if(mTargets==null){
		createTargets();
	}else{
		updateLimits();
	}
	AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
	alphaAnim.setDuration(ANIM_TARGET_TIME);
	
	//开始显示滑动的箭头
	for(ImageView v:mTargets){
		v.startAnimation(alphaAnim);
		v.setVisibility(View.VISIBLE);
	}
}


1、createTargets()方法是第一次构建滑动箭头的方法,由于我们这里只针对向上滑动的处理,因此只拿出这部分代码进行分析。其代码如下:

////代码十四
private void createTargets(){
	mTargets=new ArrayList<ImageView>();
	FrameLayout p=(FrameLayout)getParent();

	FrameLayout.LayoutParams lp=(FrameLayout.LayoutParams)getLayoutParams();
	final int horizontalGravity = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
	final int verticalGravity = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
	Starter starter=new Starter();//创建动画
	//ADW: Lets try to use the themed drawables....
	String themePackage=AlmostNexusSettingsHelper.getThemePackageName(getContext(), Launcher.THEME_DEFAULT);
	PackageManager pm=getContext().getPackageManager();
	Resources themeResources=null;
	if(!themePackage.equals(Launcher.THEME_DEFAULT)){
		try {
			themeResources=pm.getResourcesForApplication(themePackage);
		} catch (NameNotFoundException e) {
		}
	}
	
	//loadThemeResource(themeResources,themePackage,"lab_bg",mLAB,THEME_ITEM_BACKGROUND,R.drawable.lab_bg);
	if((mSlideDirections&OnTriggerListener.UP)==OnTriggerListener.UP) {
		ImageView v1 =new ImageView(getContext());
		//v1.setBackgroundResource(R.drawable.sliding_target_top);
		Launcher.loadThemeResource(themeResources,themePackage,"sliding_target_top",v1,Launcher.THEME_ITEM_BACKGROUND);
		if(v1.getBackground()==null){
			v1.setBackgroundResource(R.drawable.sliding_target_top);
		}
		AnimationDrawable frameAnimation = (AnimationDrawable) v1.getBackground();
		//frameAnimation.start();
		//将当前的动画加入到ArrayList<AnimationDrawable>中
		starter.addAnimation(frameAnimation);
		v1.setTag(OnTriggerListener.UP);
		//mTargets表示  动画箭头的区域,这里是一个ArrayList的原因是  为未来可能的左右以及上面的滑动预处理
		mTargets.add(v1);
		lp=new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
		lp.gravity=horizontalGravity;
		//frameAnimation.getIntrinsicHeight() 表示滑动箭头的高(滑动箭头组成的那个动画)
		lp.topMargin=getTop()-mTargetDistance-frameAnimation.getIntrinsicHeight();
		//将我们创建出来的滑动箭头加入到   FrameLayout中。
		p.addView(v1, lp);
		//SliderView最大的可滑动区域。这里只设置了top,也就是可以到达顶部的最大距离,而前面这个mLimitRect的左,右,下是用默认的矩形区域设置了,这就决定了
		//SlingView只能往上滑动
		mLimitRect.top=getTop()-mTargetDistance-frameAnimation.getIntrinsicHeight()-securityMargin;
		//(202,648,277,816)
		Log.i(LOG_TAG,"createTargets--mLimitRect==(" + mLimitRect.left + "," + mLimitRect.top + "," + mLimitRect.right + "," + mLimitRect.bottom +")");
	}
	//DOWN,LEFT,RIGHT的处理。
	post(starter);
}


其中Starter继承自Runnable接口,有一个增加动画的方法,run()中开始某一个动画。其代码如下:

////代码十五
class Starter implements Runnable {
	public ArrayList<AnimationDrawable> animations;
	public void run() {
		for(AnimationDrawable anim: animations){
			anim.start();
		}
	}
	public void addAnimation(AnimationDrawable anim){
		if(animations==null){
			animations=new ArrayList<AnimationDrawable>();
		}
		animations.add(anim);
	}
}


2、updateLimits()方法是在mTargets已经存在的情况下执行的,它的主要作用是重新设置可  到达区域的距离。由于一次动作中ACTION_DOWN只执行一次,每次up或者move(为进入MiniLauncher)之后,getTop()的值并不会改变,因此我们不用管。有兴趣的童鞋可以自己参看源代码。

  ACTION_DOWN中所做的事情就这些了。接下来,如果用户仅仅是点击一下SliderView(格子或者主页图标),那么程序将执行B过程,如果用户拖着SliderView移动了,那么程序将执行C过程。如果C执行的结果是LinearLayout隐藏了,当用户在DockBar的区域往下拉动的时候,程序将执行D过程以显示LinearLayout,隐藏DockBar(DockBar隐藏之后,MiniLauncher也不可见了)。

B、用户仅仅是点击SliderView,执行ACTION_UP

  由于ACTION_UP中没有break,因此执行完ACTION_UP之后会接着执行ACTION_CANCLE。从代码十二中可以看出,如果用户用户触碰SliderView的时间足够短,直接执行dispatchClickedEvent()方法,也就是展示所有应用程序的操作。这在上面(用户点击SliderView)中已经说过,这里就不再赘述。接着程序将继续往下执行ACTION_CANCLE。它的作用是将参数设置初始值,并且隐藏滑动的箭头还有就是reset(),它的作用是执行一个动画,使得SliderView从当前位置回到原始位置。这在下面将详细分析其过程。

C、用户拖着SliderView移动,执行ACTION_MOVE

  我们这里主要看一下moveHandle()方法,它是移动SliderView的核心代码。原理如下:用户每次移动都会产生一个偏移,偏移一次调用一次invalidate()方法,让父控件重绘当前的view。其代码如下:

////代码十六
/**
 * 处理移动
 * @param x
 * @param y
 */
private void moveHandle(float x, float y) {
	int deltaX=0;
	int deltaY=0;
	boolean moved=false;
	if (isHorizontal()) {
		//...
	} else {
	//下面的代码的作用是  竖直方向上产生的距离大于1/2控件高度时,开始执行滑动。它的意思就是,如果你将鼠标的位置至于SliderView的最底部,只有当
	//往上滑动一定的距离后,SliderView才开始滑动。(童鞋们可以自己测一测)
		deltaY = (int) y- (getHeight() / 2);
		if((deltaY<0 && getTop()>mLimitRect.top) || (deltaY>0 && getBottom()<mLimitRect.bottom)){
		//重新设置mTop和mBottom的值,没移动一次,都重新设置一次,然后使用这个mTop和mBottom重新绘制view,也就产生了滑动的效果。
			offsetTopAndBottom(deltaY);
			moved=true;
		}
	}
	if(moved){
		Rect rect=new Rect(getLeft()-deltaX , getTop()-deltaY, getRight()-deltaX, getBottom()-deltaY);
		ViewGroup v=(ViewGroup)getParent();
		//重绘
		v.invalidate(rect);
	}
}


moveHandle()执行完后,mTmpRect的坐标被设置成最新滑动的这个SliderView的mTop等值,接着程序调用mTargetRect.intersect(mTmpRect)判断临时矩形区域的上边界是否和目标矩形区域的下边界有交叉,如果返回true。此时程序将执行dispatchTriggerEvent()方法,从而进入MiniLauncher。这在上面的代码中已经分析过了,这里就不再赘述。如果返回的是false,而且用户已经up,此时就要执行ACTION_CANCLE中的reset(true)方法了。下面分析一下这个方法。

////代码十七
 /**
 * 重置SliderView的位置。
 * @param animate
 */
void reset(boolean animate) {
	setState(STATE_NORMAL);
	//复位
	int dx= initPosition.x-getLeft();
	int dy= initPosition.y-getTop();

	if (animate && getVisibility()==View.VISIBLE) {
		TranslateAnimation trans = new TranslateAnimation(0, dx, 0, dy);
		trans.setDuration(ANIM_DURATION);
		trans.setAnimationListener(mAnimationDoneListener);
		mAnimating=true;
		startAnimation(trans);
	} else {
		offsetLeftAndRight(dx);
		offsetTopAndBottom(dy);
		clearAnimation();
		invalidate();
	}
}


在复位的过程中,这个方法一般会被执行两次。第一次进入if条件中,开始一个动画。原因就在动画监听的onAnimationEnd()方法中。动画监听的代码如下:

////代码十八
/**
 * Listener used to reset the view when the current animation completes.
 */
private final AnimationListener mAnimationDoneListener = new AnimationListener() {
	public void onAnimationStart(Animation animation) {

	}

	public void onAnimationRepeat(Animation animation) {

	}

	public void onAnimationEnd(Animation animation) {
		onAnimationDone();
	}
};


从代码中可以看出,动画结束后onAnimationDone()方法将会被执行,其代码如下:

////代码十九
private void onAnimationDone() {
	reset(false);
	mAnimating = false;
}


offsetLeftAndRight(dx),offsetTopAndBottom(dy) 将SliderView的位置还原,然后重绘。
D、用户在DockBar的区域往下拖动,展示托盘,隐藏DockBar。

这段代码是在DockBar.java中执行的。首先程序将判断用户是否是一个滑动手势,如果是往下的滑动手势并且滑动速度大于0时,将会去执行显示托盘,隐藏DockBar的操作。代码如下:

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        	System.out.println("8888888888");
            float velocity = 0 ;
    		switch (mPosition) {
		        case LEFT:
		        	if(velocityX<0 && Math.abs(velocityY)<Math.abs(velocityX))
		        		velocity=Math.abs(velocityX);
		        	break;
		        case RIGHT:
		        	if(velocityX>0 && Math.abs(velocityY)<Math.abs(velocityX))
		        		velocity=Math.abs(velocityX);
		            break;
		        case TOP:
		        	if(velocityY<0 && Math.abs(velocityY)>Math.abs(velocityX))
		        		velocity=Math.abs(velocityY);
		        	break;
		        case BOTTOM:
                        //将执行这里
		        	if(velocityY>0 && Math.abs(velocityY)>Math.abs(velocityX))
		        		velocity=Math.abs(velocityY);
		            break;
    		}
    		Log.i(LOG_TAG, "velocity==" + velocity);
            if(velocity>0){
            	close();//关闭DockBar
            	mInterceptClicks=true;
            	return true;
            }
            return false;
        }

程序接着执行close()方法,来关闭dockBar。其代码如下:

public void close(){
		dispatchDockBarEvent(false);//关闭的具体代码
		mState=CLOSED;
        int x=0;
        int y=0;
		switch (mPosition) {
	        case LEFT:
	            x=-getWidth();
	            break;
	        case RIGHT:
	            x=getWidth();
	            break;
	        case TOP:
	            y=-getHeight();
	            break;
	        case BOTTOM:
	            y=getHeight();
	            break;
		}		
                //开一个动画
		TranslateAnimation anim=new TranslateAnimation(0, x, 0,y);
		anim.setDuration(ANIM_DURATION);
		anim.setAnimationListener(new AnimationListener() {
			
			//@Override
			public void onAnimationStart(Animation animation) {
				// TODO Auto-generated method stub
			}
			
			//@Override
			public void onAnimationRepeat(Animation animation) {
				// TODO Auto-generated method stub
			}
			
			//@Override
			public void onAnimationEnd(Animation animation) {
				// TODO Auto-generated method stub
				//将DockBar隐藏
				setVisibility(View.GONE);
			}
		});
		startAnimation(anim);
	}


在dispatchDockBarEvent(false);方法中将完成DockBar的关闭。代码如下,它也将回调Launcher.java中的onClose()方法。

private void dispatchDockBarEvent(boolean open) {
        if (mDockBarListener != null) {
        	if(open){
        		mDockBarListener.onOpen();
        	}else{
        		mDockBarListener.onClose();
        	}
        }
    }	

到这里,ADW_Launcher托盘的SliderView的点击,拖动过程就已经分析完毕。有点啰嗦,我们主要学习的是控件的滑动过程。就我所知,控件的滑动可以通过让当前view调用layout()方法重新去请求布局和调用invalidate让父控件去重绘需要重绘的部分。个人感觉第二种方式效率会更高。关于模拟SliderView滑动效果请看我的另一篇文章。模拟ADW_Launcher中SliderView的滑动操作以及可随意拖动图片的实现。其效果图如下:

Launcher的托盘 ——SliderView研究_第2张图片






 

 

 

 

 

 

 

你可能感兴趣的:(null,Integer,animation,action,float,interface)