編輯:Android開發實例
前面已實現以滑動的方式顯示或隱藏左側菜單,采用的父容器是自定義類繼承自RelativeLayout來實現的。看到有網友留言說,采用父容器自定義類繼承自ViewGroup去實現,這篇我就講解下采用繼承自ViewGroup的方式去如何實現。
自定義類讓其繼承自ViewGroup類後,編譯器強制添加一個帶Context類型參數的構造方法和onLayout()方法,代碼如下:
- package com.everyone.android.widget;
- import android.content.Context;
- import android.view.View;
- import android.view.ViewGroup;
- /**
- * 功能描述:手指在屏幕上左右滑動時,該類的實例負責讓其子View根據用戶的手勢左右偏移(滾動)
- * 父容器采用ViewGroup
- * @author android_ls
- */
- public class HorizontalScrollerContainer extends ViewGroup {
- public HorizontalScrollerContainer(Context context) {
- super(context);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) { }
- }
通過閱讀上面的代碼片段,引發的思考,ViewGroup類有無參構造方法嗎?答案是沒有;至於onLayout()方法,肯定是一個抽象方法了,那麼ViewGroup類肯定是抽象類。在Java的繼承體系中,父類(ViewGroup)中有抽象方法,子類繼承父類必須(強制性的)去實現父類的抽象方法或者把自己也定義成抽象的。既然onLayout()方法是抽象方法,我們自定義的類去繼承了ViewGroup類,最後自定義的類是要用來誕生對象的,那肯定不能也定義成抽象的。我們就去實現它,代碼如下:
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
- }
- }
既然容器有了,我們就向其中添加子View,在Activity的onCreate()方法添加如下代碼:
- // 父容器
- HorizontalScrollerContainer mSlideContainer = new HorizontalScrollerContainer(mContext);
- // 左側面板
- LeftPanelLayout mLeftPanelLayout = new LeftPanelLayout(mContext);
- LayoutParams layoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- // 添加子View
- mSlideContainer.addView(mLeftPanelLayout, layoutParams);
- // 設置要顯示的視圖
- setContentView(mSlideContainer);
我們運行程序,效果圖如下:
- public HorizontalScrollerContainer(Context context) {
- super(context);
- this.setBackgroundResource(R.drawable.v5_3_0_guide_pic1);
- }
運行效果圖:
通過上面的驗證,說明我們的父容器View是已添加到PhoneWindow上了,只是其中的子View沒有顯示出來。如何才能讓父容器中的子View顯示出來呢? 我們還得去重寫父容器ViewGroup的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,代碼如下:
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
- }
- }
運行看效果:
這次明顯達到了我們的預期(目的),效果是實現了,到底為什麼之前沒達到預期效果呢,應用框架層明明強制我(自定義類繼承自ViewGroup類)去實現的只有onLayout()方法,我按你(應用框架)的要求去做了,為什麼達不到預期效果呢?
當你為一個Activty 的PhoneWindow設置一個可見的View(ViewGroup),並且運行這個Activty時,應用框架層的調用(控制反轉)ViewGroup的顯示視圖相關方法的順序:onAttachedToWindow-->onMeasure-->onSizeChanged-->onLayout(注:其它相關的方法我們不關注,這裡就沒列出來)。下面來解釋下這幾個我們關注的方法的含義:
1、onAttachedToWindow:當View附加到一個窗體上時,應用框架層調用此方法。
2、onMeasure:View自己調用此方法,測量自己及其子View的實際寬度和高度。
3、onSizeChanged:當View大小改變時,調用此方法
4、onLayout:View自己調用此方法,為所有子View分配顯示空間的大小,可能會與子View定義時設置的大小不一樣。比如:父容器是LinearLayout,裡面的子View排列方向是豎直方式,我向父容器中添加第一個子View(A),設置寬度為fill_parent,高度為50dip;我再向父容器中添加第二個子View(B),設置寬度為fill_parent,高度也為fill_parent。在這種情況下,子View(B)的高度為多少? 是fill_parent嗎?肯定不是啦,是父容器的高度減去子View(A)的高度。(可能會與子View定義時設置的大小不一樣)
我的個人觀點:既然編寫自定義類去繼承ViewGroup後,子類必須去實現onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,為什麼不把onMeasure()方法在ViewGroup類中定義成抽象的呢?翻看源碼,會發現:
onMeasure()是定義在View類中的,源碼如下:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
ViewGroup類完全可以重寫父類的onMeasure()方法,將其修改成抽象方法。那麼當我們想自定義類去繼承ViwGroup類,都會去實現onMeasure()方法,就不會出現按應用框架要求的去做,而沒達到預期的,我們想要的結果。
編寫兩個類,進行測試,TestA類源碼:
- package com.everyone.android.widget;
- public class TestA {
- public void test(){
- System.out.println("Hello World");
- }
- }
TestB類源碼:
- package com.everyone.android.widget;
- public abstract class TestB extends TestA{
- public abstract void test();
- }
通過上面的測試,大家看到這麼寫,子類是可以修改父類的方法修飾詞的。
假如:Android應用框架裡,ViewGroup類裡重寫了父類的onMeasure()方法,在ViewGroup類中添加如下代碼:
- protected abstract void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
我們自已的類繼承ViewGroup後,我們實現onMeasure()方法的代碼如下:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- setMeasuredDimension(width, height);
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
- }
- }
至於Android應用框架層為什麼沒按我們預想的那樣去實現,自然有他們的道理,呵呵。我個人覺的若在子類中必須重寫父類的某個方法後,才既能滿足框架層又能讓使用框架的達到預期的效果。干嘛不把該方法定義成抽象的呢?何樂而不為。
好了,到這裡這一篇的重點聊完了。其它的關於用戶觸摸屏幕事件的分發、攔截和處理(響應)與前面的采用RelativeLayout做父容器時是一樣的。
采用ViewGroup做父容器,實現水平滑動顯示或隱藏左側菜單的完整代碼如下:
- 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.View;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup;
- import android.widget.Scroller;
- /**
- * 功能描述:手指在屏幕上左右滑動時,該類的實例負責讓其子View根據用戶的手勢左右偏移(滾動)
- * 父容器采用ViewGroup
- * @author android_ls
- */
- public class HorizontalScrollerContainer extends ViewGroup {
- private static final String TAG = "ScrollerContainer";
- private Scroller mScroller;
- private VelocityTracker mVelocityTracker;
- /**
- * 手柄(手把)的寬度
- */
- private int mHandlebarWidth;
- /**
- * 在偏移過程中,動畫持續的時間
- */
- private static final int ANIMATION_DURATION_TIME = 300;
- /**
- * 記錄當前的滑動結束後的狀態,左側面板是否可見
- * true 向右滑動(左側面板處於可見)
- * false 向左滑動(左側面板處於不可見)
- */
- private boolean mPanelInvisible;
- /**
- * 是否已滑動結束
- */
- private boolean mFinished;
- /**
- * 是否允許滾動
- * 滿足的條件:
- * 左側面板可見,當前手指按下的坐標x值 ,是在手柄寬度范圍內;
- * 左側面板不可見,當前手指按下的坐標x值 < 手柄寬度
- */
- private boolean mAllowScroll;
- /**
- * 是否滿足響應單擊事件的條件
- * 滿足的條件:左側面板可見,當前手指按下的坐標x值 ,是在手柄寬度范圍內
- */
- private boolean isClick;
- public HorizontalScrollerContainer(Context context) {
- super(context);
- // this.setBackgroundResource(R.drawable.v5_3_0_guide_pic1);
- mScroller = new Scroller(context);
- mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());
- }
- // 測量自己及其子View的實際寬度和高度
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
- }
- }
- /*
- * 為所有子View分配顯示空間的大小 ,可能會與子View定義時設置的大小不一樣。
- * 比如:父容器是LinearLayout,裡面的子View排列方向是豎直方式,我向父容器中添加第一個子View(A),
- * 設置寬度為fill_parent,高度為50dip;我再向父容器中添加第二個子View(B),設置寬度為fill_parent,高度也為fill_parent。
- * 在這種情況下,子View(B)的高度為多少? 是fill_parent嗎?肯定不是啦,是父容器的高度減去子View(A)的高度。
- * (可能會與子View定義時設置的大小不一樣)
- * @see android.view.ViewGroup#onLayout(boolean, int, int, int, int)
- */
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
- }
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(TAG, "dispatchTouchEvent()");
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(mFinished){
- int x = (int) ev.getX();
- int width = getWidth();
- if(mPanelInvisible)// 左側面板可見
- {
- if(x > (width - mHandlebarWidth)){ // 當前手指按下的坐標x值 ,是在手柄寬度范圍內
- isClick = true;
- mAllowScroll = true;
- return true;
- } else {
- isClick = false;
- mAllowScroll = false;
- }
- } else { // 左側面板不可見
- if(x < mHandlebarWidth ){ // 當前手指按下的坐標x值 < 手柄寬度 (也就是說在手柄寬度范圍內,是可以相應用戶的向右滑動手勢)
- mAllowScroll = true;
- }else{
- mAllowScroll = false;
- }
- }
- } else {
- // 當前正在滾動子View,其它的事不響應
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_MOVE");
- int margin = getWidth() - (int) ev.getX();
- if (margin < mHandlebarWidth && mAllowScroll) {
- Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth);
- return true;
- }
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_UP");
- if (isClick && mPanelInvisible && mAllowScroll) {
- isClick = false;
- mPanelInvisible = false;
- int scrollX = getChildAt(1).getScrollX();
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- return true;
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(TAG, "onInterceptTouchEvent()");
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(!mFinished){
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_MOVE");
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(ev);
- // 一秒時間內移動了多少個像素
- mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());
- float velocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;
- Log.d(TAG, "onInterceptTouchEvent(): mVelocityValue = " + velocityValue);
- if (velocityValue > 300 && mAllowScroll) {
- return true;
- }
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_UP");
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- 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, "onTouchEvent()");
- float x = event.getX();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(!mFinished){
- return false;
- }
- 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");
- if(!mAllowScroll){
- break;
- }
- float width = getWidth();
- // 響應滾動子View的臨界值,若覺得響應過於靈敏,可以將只改大些。
- // 比如:criticalWidth = width / 3或criticalWidth = width / 2,看情況而定,呵呵。
- float criticalWidth = width / 5;
- Log.i(TAG, "onTouchEvent(): ACTION_UP x = " + x + "\t criticalWidth = " + criticalWidth);
- int scrollX = getChildAt(1).getScrollX();
- if ( x < criticalWidth) {
- Log.i(TAG, "onTouchEvent(): ACTION_UP 向左滑動");
- mPanelInvisible = false;
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- } else if ( x > criticalWidth){
- Log.i(TAG, "onTouchEvent(): ACTION_UP 向右滑動");
- mPanelInvisible = true;
- 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();
- }
- }
- /**
- * 向右滑動View,讓左側操作面飯可見
- */
- public void slideToRight() {
- mFinished = mScroller.isFinished();
- if(mFinished && !mPanelInvisible){
- mPanelInvisible = true;
- 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();
- }
- }
測試的Activity源碼如下:
- package com.everyone.android.ui;
- import android.os.Bundle;
- import android.view.ViewGroup.LayoutParams;
- import com.everyone.android.AppBaseActivity;
- import com.everyone.android.widget.FreshNewsLayout;
- import com.everyone.android.widget.HorizontalScrollerContainer;
- import com.everyone.android.widget.HorizontalScrollerContainer.OnSlideListener;
- import com.everyone.android.widget.LeftPanelLayout;
- /**
- * 功能描述:應用主界面
- * @author android_ls
- *
- */
- public class EveryoneActivity extends AppBaseActivity implements OnSlideListener {
- /**
- * 滾動(滑動)容器
- */
- private HorizontalScrollerContainer mSlideContainer;
- /**
- * 左側面板
- */
- private LeftPanelLayout mLeftPanelLayout;
- /**
- * 新鮮事
- */
- private FreshNewsLayout mFreshNewsLayout;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(mSlideContainer);
- }
- @Override
- protected int getLayoutId() {
- return 0;
- }
- @Override
- protected void setupView() {
- mSlideContainer = new HorizontalScrollerContainer(mContext);
- mLeftPanelLayout = new LeftPanelLayout(mContext);
- mFreshNewsLayout = new FreshNewsLayout(mContext);
- mFreshNewsLayout.setOnSlideListener(this);
- LayoutParams layoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- mSlideContainer.addView(mLeftPanelLayout, layoutParams);
- mSlideContainer.addView(mFreshNewsLayout, layoutParams);
- }
- @Override
- protected void initialized() {
- // TODO Auto-generated method stub
- }
- @Override
- public void toLeft() {
- // TODO Auto-generated method stub
- }
- @Override
- public void toRight() {
- mSlideContainer.slideToRight();
- }
- }
運行效果圖:
轉自:http://blog.csdn.net/android_ls/article/details/8761410
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
Android提供了許多方法來控制播放的音頻/視頻文件和流。其中該方法是通過一類稱為MediaPlayer。Android是提供MediaPlayer類訪問內置的媒體播放
本文實例展示了Android中實現為TextView添加多個可點擊的文本的方法。該功能在Android社交軟件的制作中非常具有實用價值。分享給大家供大家參考。具體
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個