Android實現浮層的上下滑動(支持內部添加View)

前言

 

我K,今天居然是情人節,對於資深的單身狗來說,簡直是個噩耗,今天註定是各種秀恩愛,心塞中。。。。

 

話題到此結束,管他什麼情人節,今天給大傢帶來的是一個浮層的上下滑動,浮層滑動時分三種狀態:全部顯示、顯

 

示一半、隱藏。可在浮層中添加ListView,GirdView,ImageView等等View。

 

具體的效果看下面的GIF圖:

 

 

 

 

 

 

 

 

效果講解

 

 

1、在上面的浮層中我們可以看到存放著一個ListView,並能進行上下滾動,也就是說浮層的Touch事件需要在適

 

當的時候進行攔截,不傳遞給子View。這時需要重寫onInterceptTouchEvent()和onTouch()方法。

 

onInterceptTouchEvent和onTouch的介紹請參看下面:

 

 

1、onInterceptTouchEvent()是用於處理事件(類似於預處理,當然也可以不處理)並改變事件的傳遞方向,也就是

 

 

決定是否允許Touch事件繼續向下(子控件)傳遞,一但返回True(代表事件在當前的viewGroup中會被處理),則向

 

 

下傳遞之路被截斷(所有子控件將沒有機會參與Touch事件),同時把事件傳遞給當前的控件的onTouchEvent()處

 

理;返回false,則把事件交給子控件的onInterceptTouchEvent()。

 

 

2、onTouchEvent()用於處理事件,返回值決定當前控件是否消費(consume)瞭這個事件,也就是說在當前控件在處

 

 

理完Touch事件後,是否還允許Touch事件繼續向上(父控件)傳遞,一但返回True,則父控件不用操心自己來處理

 

 

Touch事件。返回true,則向上傳遞給父控件(註:可能你會覺得是否消費瞭有關系嗎,反正我已經針對事件編寫瞭

 

 

處理代碼?答案是有區別!比如ACTION_MOVE或者ACTION_UP發生的前提是一定曾經發生瞭ACTION_DOWN,如果你沒有

 

消費ACTION_DOWN,那麼系統會認為ACTION_DOWN沒有發生過,所以ACTION_MOVE或者ACTION_UP就不能被捕獲。)

 

2、在上圖中可以看出,當進行上下滾動時具有滾動效果,可以通過以下代碼實現:

 

 

ObjectAnimator anim=ObjectAnimator.ofFloat(this, translationY, values);

 

實現原理

 

實現的原理其實很簡單,我們所要做的就是在onInterceptTouchEvent和onTouch兩個事件處理的方法中進行處理。

onInterceptTouchEvent的任務是判斷何時攔截事件,交由onTouch處理,何時由子View進行事件處理。

 

 

 

根據上圖可以很清楚知道onInterceptTouchEvent所要做的工作。在onTuch的中最主要的操作是在獲取滑動時的

 

MotionEvent.ACTION_MOVE中進行動畫的處理。

 

到此為止隻說講解瞭兩個事件處理方法該做寫什麼,在圖中可以看出,滑動時主要有三種狀態,分別是全部顯示

 

、顯示一半、隱藏。

 

全部顯示:當浮層全部顯示時。這時浮層視圖不應該繼續向上滾動,這時需要在onInterceptTouchEvent中將事

 

件傳遞給它的子View進行處理;當向下滑動時,需要進行向下移動的動畫處理,滑動一次,這時的浮層視圖應該顯示

 

一半。

 

顯示一半:當浮層顯示一半時。這時進行向上滑動時需要執行向上移動的動畫處理,向下滑動時也一樣,進行移

 

動的動畫處理,進行隱藏浮層。

 

隱藏:當浮層進行隱藏時。可以通過點擊相應的點擊事件,使浮層從底部向上移動,並移動一半。

 

 

代碼

 

以上是大體的思路,具體實現請參看下面的代碼。

 

 

public class FloatingLayerView extends LinearLayout implements OnTouchListener {

	/**
	 * 視圖顯示類型。
	 */
	private int type=ALL;
	/**
	 * 浮層的高度。
	 */
	private int floating_height;
	/**
	 * 浮層的寬度
	 */
	private int floating_width;
	
	/**
	 * 滑動高度
	 */
	private float move_height;
	
	/**
	 * 是否向下滑動,交由onTouch事件處理。
	 */
	private boolean isCanHide=false;
	/**
	 * 是否進行動畫
	 */
	private boolean isCanAnimation=false;
	
	/**
	 * 觸發攔截觸摸事件時的坐標點。 
	 * 按下:
	 *  interceptTouch_X:按下時的X坐標點。
	 *  interceptTouch_Y:按下時的Y坐標點。
	 * 滑動: 
	 * interceptMove_X:滑動時的X坐標點。 
	 * interceptMove_Y:滑動時的Y坐標點。 
	 * 距離:
	 * interceptTouch_Move_X:從按下到滑動之間的距離(橫向滑動)
	 * interceptTouch_Move_Y:從按下到滑動之間的距離(縱向滑動)
	 * 滑動距離:
	 * moveLength:根據此值判斷是否進行瞭滑動。
	 */
	private float interceptTouch_X;
	private float interceptTouch_Y;
	private float interceptMove_X;
	private float interceptMove_Y;
	private float interceptTouch_Move_X;
	private float interceptTouch_Move_Y;
	private int moveLength=10;
	
	/**
	 * 觸發觸摸事件時的坐標點
	 * down_X:按下時的X坐標點。
	 * down_Y:按下時的Y坐標點。
	 * move_X:移動時的X坐標點。
	 * move_Y:移動時的Y坐標點。
	 * down_move_X:橫向滑動的距離。
	 * down_move_Y:縱向滑動的距離。
	 */
	private float down_X;
	private float down_Y;
	private float move_X;
	private float move_Y;
	private float down_move_X;
	private float down_move_Y;

	/**
	 * 定義三種浮層顯示類型 
	 * 0:不顯示 1:顯示一半 2:全部顯示
	 */
	private static final int NONE=0;
	private static final int HALF=1;
	private static final int ALL=2;
	
	public FloatingLayerView(Context context, AttributeSet attrs) {
		super(context, attrs);
		setOnTouchListener(this);
	}

	public FloatingLayerView(Context context) {
		super(context);
	}
	
	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		if(hasWindowFocus){
			floating_width=getWidth();
			floating_height=getHeight();
			/**
			 * 每次滑動的距離是當前View寬度的三分之一。
			 */
			move_height=floating_height/3;
		}
		super.onWindowFocusChanged(hasWindowFocus);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		switch (ev.getAction()) {
		/**
		 * 當按下時獲取x,y的坐標點。
		 */
		case MotionEvent.ACTION_DOWN:
			interceptTouch_X = ev.getX();
			interceptTouch_Y = ev.getY();
			isCanAnimation=true;
			break;
			/**
			 * 當滑動時操作如下:
			 * 1、獲取滑動距離
			 * 2、判斷向上滑動還是向下滑動
			 * 3、向上滑動時
			 * 4、向下滑動時,判斷當前顯示方式:
			 * (1)、顯示一半時,交由onTouch事件處理。
			 * (2)、全部顯示時,是否向下滑動交由當前View的子View處理,
			 * 是否交由onTouch事件處理。
			 */
		case MotionEvent.ACTION_MOVE:
			interceptMove_X = ev.getX();
			interceptMove_Y = ev.getY();
			interceptTouch_Move_X = Math
					.abs(interceptTouch_X - interceptMove_X);
			interceptTouch_Move_Y = Math
					.abs(interceptTouch_Y - interceptMove_Y);
			/**
			 * 向下滑動
			 */
			if(interceptMove_Y>interceptTouch_Y&&interceptTouch_Move_Y>moveLength&&interceptTouch_Move_Y>interceptTouch_Move_X){
				return isDounTransferOnTouch();
			}
			/**
			 * 向上滑動
			 */
			if(interceptTouch_Y>interceptMove_Y&&interceptTouch_Move_Y>moveLength&&interceptTouch_Move_Y>interceptTouch_Move_X){
				return isUpTransferOnTouch();
			}
			break;
		case MotionEvent.ACTION_UP:
			break;
		default:
			break;
		}
		return super.onInterceptTouchEvent(ev);
	}
	
	
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			break;
			/**
			 * 當滑動時動畫操作
			 */
		case MotionEvent.ACTION_MOVE:
			down_X=interceptTouch_X;
			down_Y=interceptTouch_Y;
			move_X=event.getX();
			move_Y=event.getY();
			down_move_X=Math.abs(down_X-move_X);
			down_move_Y=Math.abs(down_Y-move_Y);
			/**
			 * 向下滑動
			 */
			if(move_Y>down_Y&&down_move_Y>moveLength&&getCanAnimation()){
				downAnimationConfig();
			}
			/**
			 * 向上滑動
			 */
			if(down_Y>move_Y&&down_move_Y>moveLength&&getCanAnimation()){
				upAnimationConfig();
			}
			/**
			 * 執行完上面動畫處理後,停止執行動畫
			 */
			setCanAnimation(false);
			break;
		case MotionEvent.ACTION_UP:
			break;
		default:
			break;
		}
		return true;
	}
	
	/**
	 * 是否進行動畫處理
	 * @return true:處理
	 */
	private boolean getCanAnimation(){
		return isCanAnimation;
	}
	
	/**
	 * 獲取當前視圖顯示類型
	 * @return
	 */
	private int getType(){
		return type;
	}
	
	private void setType(int type){
		this.type=type;
	}
	
	/**
	 * 設置是否進行動畫處理
	 * @param canAnimation
	 */
	private void setCanAnimation(boolean canAnimation){
		this.isCanAnimation=canAnimation;
	}

	/**
	 * 向下滑動時的動畫處理
	 */
	private void downAnimationConfig(){
		switch (getType()) {
		case HALF://當視圖顯示一半時
			half2None();
			break;
		case ALL://當視圖全部顯示時
			all2Half();
			break;

		default:
			break;
		}
	}
	
	/**
	 * 向上滑動時的動畫處理
	 */
	private void upAnimationConfig(){
		switch (getType()) {
		case HALF://當視圖顯示一半時
			half2All();
			break;
		case ALL://當視圖全部顯示時
			/**
			 * 當視圖已經完整顯示,再往
			 * 上滑動也就沒任何意義進行
			 * 動畫處理。
			 */
			break;
		default:
			break;
		}
	}
	
	/**
	 * 向下滑動時是否交由onTouch事件處理
	 * @return true:由onTouch事件處理,不傳遞給子View
	 */
	private boolean isDounTransferOnTouch(){
		switch (type) {
		case NONE:
			break;
		case HALF:
			return true;
		case ALL:
			if(isCanHide){
				return true;
			}
			break;
		default:
			break;
		}
		return false;
	}
	
	/**
	 * 向上滑動時是否交由onTouch事件處理
	 * @return true:由onTouch事件處理,不傳遞給子View
	 */
	private boolean isUpTransferOnTouch(){
		switch (type) {
		case NONE:
			break;
		case HALF:
			return true;
		case ALL:
			break;
		default:
			break;
		}
		return false;
	}
	
	
	/**
	 * 當向下滑動時,當前視圖顯示一半,再往下滑動隱藏。
	 * type設置為NONE
	 */
	private void half2None(){
		float[] values=new float[]{move_height,getHeight()};
		startAnimation(values);
		setType(NONE);
	}
	
	/**
	 * 當向下滑動時,當前視圖顯示完整,再往下滑動視圖顯示一半。
	 * type設置為HALF
	 */
	private void all2Half(){
		float[] values=new float[]{0,move_height};
		startAnimation(values);
		setType(HALF);
	}
	
	/**
	 * 當向上滑動時,當前視圖顯示一半,再往上滑動,視圖顯示完整。
	 * type設置為ALL
	 */
	private void half2All(){
		float[] values=new float[]{move_height,0};
		startAnimation(values);
		setType(ALL);
	}
	
	/**
	 * 執行動畫
	 * @param values
	 */
	private void startAnimation(float[] values){
		AnimatorSet as=new AnimatorSet();
		ObjectAnimator anim=ObjectAnimator.ofFloat(this, translationY, values);
		anim.setDuration(500);
		as.playTogether(anim);
		as.start();
	}
	
	/**
	 * 當前視圖顯示完整時的動畫處理
	 */
	private void all2None(){
		float[] values=new float[]{0,getHeight()};
		startAnimation(values);
		setType(HALF);
	}
	
	/**
	 * 隱藏浮層
	 */
	public void beforeInput(){
		switch (getType()) {
		case NONE:	
			break;
		case HALF:
			half2None();
			break;
		case ALL:
			all2None();
			break;

		default:
			break;
		}
	}
	
	/**
	 * 顯示浮層一半
	 */
	public void none2Half(){
		float[] values=new float[]{getHeight(),move_height};
		startAnimation(values);
		setType(HALF);
	}
	
	/**
	 * 顯示全部浮層
	 */
	public void none2All(){
		float[] values=new float[]{getHeight(),0};
		startAnimation(values);
		setType(HALF);
	}
	
	/**
	 * 是否進行動畫滾動
	 * @param canHide
	 */
	public void setCanHide(boolean canHide){
		this.isCanHide=canHide;
	}

}

 

在代碼中已經進行瞭很詳細的註釋,在代碼的最後暴露瞭幾個可調用的方法,可以通過這幾個方法實現我們的滑動效

 

果。

 

在Activity中的通過GridView的OnScrollListener監聽事件進行判斷何時進行動畫的滾動,何時停止,當然在FloatingLa

 

yerView可以加上想要加的View。例如以下在FloatingLayerView中添加瞭GirdView:

 

 

 

 

Activity的代碼如下:

 

 

public class MainActivity extends Activity implements OnClickListener {

	private Button btn_show;
	private Button btn_hide;
	private GridView gv_all;

	private TestAdapter testAdapter = new TestAdapter();


	// 覆蓋層
	private FloatingLayerView mFloatingLayerView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		addListener();
	}

	private void initView() {
		btn_show = (Button) findViewById(R.id.btn_show);
		btn_hide = (Button) findViewById(R.id.btn_hide);
		// 覆蓋層
		mFloatingLayerView = (FloatingLayerView) findViewById(R.id.activity_shine_ll_cover);
		gv_all = (GridView) findViewById(R.id.activity_shine_gv_all);

		gv_all.setAdapter(testAdapter);

	}

	private void addListener() {
		btn_show.setOnClickListener(this);
		btn_hide.setOnClickListener(this);
		gv_all.setOnScrollListener(scrollListener);

	}


	@Override
	public void onClick(View v) {
		switch (v.getId()) {

		// 顯示浮層
		case R.id.btn_show:
			mFloatingLayerView.none2Half();
			break;

		// 隱藏浮層
		case R.id.btn_hide:
			mFloatingLayerView.beforeInput();
			break;

		}
	}

	

	/** 覆蓋層中GridView滑動監聽 */
	private OnScrollListener scrollListener = new OnScrollListener() {

		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
		}

		@Override
		public void onScroll(AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount) {
			if (firstVisibleItem == 0) {
				mFloatingLayerView.setCanHide(true);
			} else {
				mFloatingLayerView.setCanHide(false);
			}

		}

	};


	// =============測試======================
	private int[] images = new int[] { R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher, R.drawable.ic_launcher, R.drawable.ic_launcher,
			R.drawable.ic_launcher };

	class TestAdapter extends BaseAdapter {

		@Override
		public int getCount() {
			return images.length;
		}

		@Override
		public Object getItem(int position) {
			return images[position];
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			View view = LayoutInflater.from(MainActivity.this).inflate(
					R.layout.image, null);
			
			ImageView imagView = (ImageView) view.findViewById(R.id.iv_show);
			imagView.setBackgroundResource(images[position]);
			return view;
		}

	}

}

OK,至此滑動的浮層已經實現瞭,歡迎大傢吐槽。

 

 

 

 

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *