編輯:Android開發實例
一、滑動效果的實現原理:
1、采用RelativeLayout作為父容器, 當調用addView(View child)方法向其中添加子View(子View采用FrameLayout),並且其子View的布局參數都設置的是填充整個父容器的大小(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT))。那麼父容器中當前顯示的應該是最後添加的View。
2、 要實現子View可以在父容器中滑動,那麼我們就得重寫父容器的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法,攔截用戶觸屏手勢並作出判斷和事件處理。比如,當前用戶的某個(單擊、向左滑動和向右滑動等)觸屏事件,是否需要響應,若要響應,是父容器自己去處理呢,還是應該交給父容器裡的某個子View去處理。
3、要做到第2小點中提到的,必須先了解ViewGrop的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)回調方法,在整個View樹(應用框架)中的調用先後順序及其返回值所代表的含義。下面做個小測試,前提:ViewGroup(父容器中的一個子View)有子View,並且子View中的View有事件處理器(比如,子View是Button,事件處理器指的就是Button的點擊事件監聽器中的onClick(View v)方法)或者子View可以獲得焦點(比如選中效果)。
自定義類繼承RelativeLayout類,如下:
- public class ParentContainer extends RelativeLayout {
- private static final String TAG = "ParentContainer";
- public ParentContainer(Context context) {
- super(context);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");
- return super.onInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(TAG, "ParentContainer : onTouchEvent()");
- return super.onTouchEvent(event);
- }
- }
其子View代碼如下:
- public class ChildContainer extends FrameLayout {
- private static final String TAG = "ChildContainer";
- public ChildContainer(Context context) {
- super(context);
- Button btnTest = new Button(context);
- btnTest.setText("測試按鈕");
- btnTest.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.i(TAG, "ChildContainer : 我響應了單擊事件");
- }
- });
- LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- this.addView(btnTest, params);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.i(TAG, "ChildContainer : onInterceptTouchEvent()");
- return super.onInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.i(TAG, "ChildContainer : onTouchEvent()");
- return super.onTouchEvent(event);
- }
- }
測試Activity代碼:
- public class TestActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ParentContainer mSlideContainer = new ParentContainer(this);
- ChildContainer childContainer = new ChildContainer(this);
- LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- mSlideContainer.addView(childContainer, params);
- setContentView(mSlideContainer);
- }
- }
a. 默認情況下,單擊子View中的Button按鈕,LogCat打印Log如下:
修改父容器的onInterceptTouchEvent()返回值為false,單擊子View的按鈕,打印Log與默認值一樣。跟蹤源碼發現其實默認返回值就是false。修改返回值為true,單擊子View的按鈕,LogCat打印Log如下:
結論:父容器中onInterceptTouchEvent()方法的返回值為true時,表示將事件交給ViewGroup自己的onTouchEvent()去處理;返回值為false時,表示將事件交給ViewGroup的子View的onInterceptTouchEvent()去處理。(默認的處理方式)
b. 父容器使用默認的值,修改子View的onTouchEvent()方法返回值為false,單擊子View,LogCat打印Log如下:
修改子View的onTouchEvent()方法返回值為true,單擊子View,LogCat打印Log如下:
結論:父容器使用默認的值,修改子View的onTouchEvent()方法返回值為false,表示將事件交父View處理;修改子View的onTouchEvent()方法返回值為true,表示該事件子View自己已經處理了,到這裡終止。
4、在父容器中,攔截用戶觸屏手勢後,想交給父容器自己去處理,或者是想交給父容器裡的某個子View去處理,應該怎麼實現,通過上面的講解,我想大家已經明白了,決定事件的傳遞順序或在那個View裡終止傳遞,是通過ViewGroup中的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法的返回值來決定的。接下來開始聊如何實現父容器中的子View的左右滑動(滾動),這裡就用到了Scroller和VelocityTracker兩個類。
a. 為什麼要用Scroller類? 如果實現想把一個View偏移至指定坐標(x,y)處,利用View類提供的scrollTo()方法直接調用就可以了。但是View類的scrollTo()方法是非常迅速的將View從一個坐標點(20, 0)移到另一個坐標點(300, 0),而沒有對這個偏移過程有任何控制,對用戶而言這件事發生的很突然,用戶體驗不好。而Scroller類提供的startScroll()方法,在偏移過程中添加了動畫,提升了用戶體驗。因此我們選擇使用Scroller類的對象來實現View的偏移。
b. VelocityTracker類,主要用跟蹤觸摸屏事件(flinging事件和其他gestures手勢事件)的速率。 用addMovement(MotionEvent)函數將Motion event加入到VelocityTracker類實例中。你可以使用getXVelocity() 或getXVelocity()獲得橫向和豎向的速率到速率時,但是使用它們之前請先調用computeCurrentVelocity(int)來初始化速率的單位 。
關於computeCurrentVelocity(int units, float maxVelocity) 方法的參數列表解釋:
int unitis表示速率的基本時間單位。unitis值為1的表示是,一毫秒時間單位內運動了多少個像素, unitis值為1000表示一秒(1000毫秒)時間單位內運動了多少個像素。
float maxVelocity表示速率的最大值。
二、按上面的講解思路編碼實現:
1、滑動方式實現:
只在父容器的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法中,添加MotionEvent.ACTION_MOVE的事件處理。
父容器類代碼如下:
- package com.everyone.android.widget;
- import android.content.Context;
- import android.util.Log;
- import android.util.TypedValue;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.ViewConfiguration;
- import android.widget.RelativeLayout;
- import android.widget.Scroller;
- /**
- * 功能描述:手指在屏幕上左右滑動時,該類的實例負責其的子View的左右偏移(滾動)
- * @author android_ls
- */
- public class ScrollerContainer extends RelativeLayout {
- private static final String TAG = "ScrollerContainer";
- private Scroller mScroller;
- private VelocityTracker mVelocityTracker;
- /**
- * 手柄(手把)的寬度
- */
- private int mHandlebarWidth;
- /**
- * 一秒時間內移動了多少個像素
- */
- private float mVelocityValue;
- /**
- * 在偏移過程中,動畫持續的時間
- */
- private static final int ANIMATION_DURATION_TIME = 300;
- public ScrollerContainer(Context context) {
- super(context);
- mScroller = new Scroller(context);
- mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(ev);
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_MOVE");
- mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());
- mVelocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;
- Log.d(TAG, "onInterceptTouchEvent(): mVelocityValue = " + mVelocityValue);
- if (mVelocityValue > 300) {
- return true;
- }
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.onInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(TAG, "ParentContainer : onTouchEvent()");
- float x = event.getX();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onTouchEvent(): ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "onTouchEvent(): ACTION_MOVE");
- getChildAt(1).scrollTo(-(int)x, 0);
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "onTouchEvent(): ACTION_UP");
- float width = getWidth();
- float halfWidth = width / 2;
- Log.i(TAG, "onTouchEvent(): ACTION_UP x = " + x + "\t halfWidth = " + halfWidth);
- int scrollX = getChildAt(1).getScrollX();
- if ( x < halfWidth) {
- Log.i(TAG, "onTouchEvent(): ACTION_UP 向左滑動");
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- } else if ( x > halfWidth){
- Log.i(TAG, "onTouchEvent(): ACTION_UP 向右滑動");
- int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);
- mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "onTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public void computeScroll() {
- // super.computeScroll();
- if(mScroller.computeScrollOffset()){
- this.getChildAt(1).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
- this.postInvalidate();
- }
- }
- }
子容器就是兩個繼承自FrameLayout的Layout,源碼就不貼了。
測試類代碼如下:
- package com.everyone.android.ui;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.ViewGroup.LayoutParams;
- import com.everyone.android.widget.FreshNewsLayout;
- import com.everyone.android.widget.LeftPanelLayout;
- import com.everyone.android.widget.ScrollerContainer;
- public class TestActivity extends Activity {
- /**
- * 左側面板
- */
- private LeftPanelLayout mLeftPanelLayout;
- /**
- * 新鮮事
- */
- private FreshNewsLayout mFreshNewsLayout;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ScrollerContainer mSlideContainer = new ScrollerContainer(this);
- LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- mLeftPanelLayout = new LeftPanelLayout(this.getApplicationContext());
- mFreshNewsLayout = new FreshNewsLayout(this.getApplicationContext());
- mSlideContainer.addView(mLeftPanelLayout, params);
- mSlideContainer.addView(mFreshNewsLayout, params);
- setContentView(mSlideContainer);
- }
- }
運行效果圖如下:
2、單擊方式實現:
左右滑動實現子View的滾動,有一個臨界值,一秒時間內移動了的像素數要大於某個預設的值,才會觸動相應事件處理器。那麼為了用戶體驗好點,我們提供另外一種操作方式,那就是單擊事件。假設ViewGroup中有兩個子View A和B,B處於A上面,兩個子View是疊在一起的。默認顯示的是B,並占據著整個手機屏幕,A是看不見的。為了能看見A,並且可以操作,我們需要把B視圖(子View)移動一定單位,可以在B視圖中添加子View(Button)並綁定事件監聽器。當用戶點擊B視圖中的特定子View(Button)時,讓B視圖偏移一定單位,我麼就能看見A視圖;當B處於A上並偏移了一定的單位,這時單擊B,實現B視圖移動到回去(恢復默認顯示)。
a. 單擊B,實現B視圖移動到回去(恢復默認顯示),代碼如下:
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(ev);
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_DOWN");
- int x = (int) ev.getX();
- int width = getWidth();
- if(x >= (width - mHandlebarWidth)){
- isClick = true;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_MOVE");
- mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());
- mVelocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;
- Log.d(TAG, "onInterceptTouchEvent(): mVelocityValue = " + mVelocityValue);
- if (mVelocityValue > 300) {
- return true;
- }
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_UP");
- if (isClick) {
- isClick = false;
- int scrollX = getChildAt(1).getScrollX();
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME); invalidate();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.onInterceptTouchEvent(ev);
- }
b. 當用戶點擊B視圖中的特定子View(Button)時,讓B視圖偏移一定單位。在父容器中添加滑動事件監聽器和向右滑動的實現,代碼如下:
- /**
- * 向右滑動View,讓左側操作面飯可見
- */
- public void slideToRight() {
- float width = getWidth();
- int scrollX = getChildAt(1).getScrollX();
- int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);
- mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- }
- /**
- * View滑動事件監聽器
- * @author android_ls
- */
- public interface OnSlideListener {
- /**
- * 向做滑動View
- */
- public abstract void toLeft();
- /**
- * 向右滑動View
- */
- public abstract void toRight();
- }
子視圖FreshNewsLayout的源碼:
- package com.everyone.android.widget;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.widget.FrameLayout;
- import android.widget.LinearLayout;
- import com.everyone.android.R;
- import com.everyone.android.widget.ScrollerContainer.OnSlideListener;
- /**
- * 功能描述:新鮮事視圖
- * @author android_ls
- */
- public class FreshNewsLayout extends FrameLayout {
- public LinearLayout llBack;
- private OnSlideListener mOnSlideListener;
- public FreshNewsLayout(Context context) {
- super(context);
- setupViews();
- }
- public FreshNewsLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- setupViews();
- }
- public void setOnSlideListener(OnSlideListener onSlideListener) {
- mOnSlideListener = onSlideListener;
- }
- private void setupViews() {
- final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());
- LinearLayout rlTopNavbar = (LinearLayout) mLayoutInflater.inflate(R.layout.fresh_news, null);
- addView(rlTopNavbar);
- llBack = (LinearLayout) rlTopNavbar.findViewById(R.id.ll_back);
- llBack.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- if (mOnSlideListener != null) {
- mOnSlideListener.toRight();
- }
- }
- });
- }
- }
測試類代碼如下:
- package com.everyone.android.ui;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.ViewGroup.LayoutParams;
- import com.everyone.android.widget.FreshNewsLayout;
- import com.everyone.android.widget.LeftPanelLayout;
- import com.everyone.android.widget.ScrollerContainer;
- import com.everyone.android.widget.ScrollerContainer.OnSlideListener;
- public class TestActivity extends Activity implements OnSlideListener {
- /**
- * 左側面板
- */
- private LeftPanelLayout mLeftPanelLayout;
- /**
- * 新鮮事
- */
- private FreshNewsLayout mFreshNewsLayout;
- /**
- * 滾動(滑動)容器
- */
- private ScrollerContainer mSlideContainer;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mSlideContainer = new ScrollerContainer(this);
- LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- mLeftPanelLayout = new LeftPanelLayout(this.getApplicationContext());
- mFreshNewsLayout = new FreshNewsLayout(this.getApplicationContext());
- mFreshNewsLayout.setOnSlideListener(this);
- mSlideContainer.addView(mLeftPanelLayout, params);
- mSlideContainer.addView(mFreshNewsLayout, params);
- setContentView(mSlideContainer);
- }
- @Override
- public void toLeft() {
- // TODO Auto-generated method stub
- }
- @Override
- public void toRight() {
- mSlideContainer.slideToRight();
- }
- }
轉自:http://blog.csdn.net/android_ls/article/details/8756059
Android提供了許多方法來控制播放的音頻/視頻文件和流。其中該方法是通過一類稱為MediaPlayer。Android是提供MediaPlayer類訪問內置的媒體播放
Android提供了許多方法來控制播放的音頻/視頻文件和流。其中該方法是通過一類稱為MediaPlayer。Android是提供MediaPlayer類訪問內置的媒體播放
仿水波紋流球進度條控制器,Android實現高端大氣的主流特效,供大家參考,具體內容如下 效果圖: CircleView 這裡主要是實現中心圓以及水波特效
登錄應用程序的屏幕,詢問憑據登錄到一些特定的應用。可能需要登錄到Facebook,微博等本章介紹了,如何創建一個登錄界面,以及如何管理安全問題和錯誤嘗試。首先,必須定義兩