Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中的Toast源碼分析和自定義Toast

Android中的Toast源碼分析和自定義Toast

編輯:關於Android編程

一、系統自帶Toast的源碼分析

1. Toast的調用顯示

學過Android的人都知道,彈出一個系統API吐司只需要一行代碼,調用的是Toast對象的makeText()方法,方法裡給一個上下文,顯示的文字,和顯示的時長,然後再調用show方法就能顯示。

Toast.makeText(this,"下載失敗",Toast.LENGTH_SHORT).show();

2. Toast對象的makeText()方法分析

跟進Toast類的源碼,找到makeText()方法,可以看到這是個靜態方法,並且返回值仍然是Toast,所以在調用了makeText()方法之後,可以繼續調用show()方法。 這個方法一進來就根據上下文創建了一個吐司對象,最後返回的也是這個吐司對象,而最後吐司的顯示就是用這個對象裡面的方法,所以我們要跟進這個Toast對象的構造。

方法裡面的代碼就是獲取一個布局填充器,然後將系統布局文件轉換為View的對象,再使用這個View對象裡邊的TextView控件顯示設置的文本。

public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
    // 根據傳進來的上下文,創建一個吐司對象
    Toast result = new Toast(context);

    // 布局填充器
    LayoutInflater inflate = (LayoutInflater)
            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    // 填充系統布局文件,轉換為View對象
    View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);

    // 獲取TextView文本對象,設置顯示的文字
    TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
    tv.setText(text);

    // 初始化顯示的時長
    result.mNextView = v;
    result.mDuration = duration;

    // 返回處理過的吐司對象
    return result;
 }

3. Toast對象的show()方法分析

跟進第二步創建的Toast構造方法,可以看到裡面並沒有關於show()方法的蹤跡,但是我們可以很清楚的發現,它又創建了一個TN對象。其他的代碼都是做一些初始化操作。

public Toast(Context context) {
    mContext = context;
    mTN = new TN();
    mTN.mY = context.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.toast_y_offset);
    mTN.mGravity = context.getResources().getInteger(
            com.android.internal.R.integer.config_toastDefaultGravity);
}

繼續跟進TN對象,可以發現這是個內部類,而且關於吐司的大部分代碼都是在裡面實現的。先來看一下構造方法,這裡面只做了一件事情,就是創建了窗口管理器並且進行初始化,這個初始化的窗體其實就是談吐司的窗體。

 TN() {
    // XXX This should be changed to use a Dialog, with a Theme.Toast
    // defined that sets up the layout params appropriately.
    // 窗口管理器,布局參數對象
    final WindowManager.LayoutParams params = mParams;

    // 設置吐司的寬高是包裹內容
    params.height = WindowManager.LayoutParams.WRAP_CONTENT;
    params.width = WindowManager.LayoutParams.WRAP_CONTENT;
    // 透明
    params.format = PixelFormat.TRANSLUCENT;

    // 吐司彈出動畫
    params.windowAnimations = com.android.internal.R.style.Animation_Toast;
    params.type = WindowManager.LayoutParams.TYPE_TOAST;
    params.setTitle("Toast");

    // 設置吐司窗體的標識
    params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}

我們再來看看TN類的其他方法,很快我們就找到了一個特別明顯的方法就是show()方法和hide()方法,而這兩個方法正是我們要的,繼續找到mHandler的方法。

// show()方法和hide()方法    
 @Override
public void show() {
    if (localLOGV) Log.v(TAG, "SHOW: " + this);
    mHandler.post(mShow);
}

/**
 * schedule handleHide into the right thread
 */
@Override
public void hide() {
    if (localLOGV) Log.v(TAG, "HIDE: " + this);
    mHandler.post(mHide);
}

在mHandler的方法中,我們發現最後是調用了handleShow()和handlerHide()方法,這兩個方法裡有一句代碼特別重要,因為這句代碼就是讓吐司顯示出來的代碼。

 // 顯示
 public void handleShow() {
        ...

        // 窗口管理器
        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

        ...
        // 判斷View對象的父控件不為空
        if (mView.getParent() != null) {
            mWM.removeView(mView);
        }

        // 將當前的View添加到窗口管理器中,這句代碼能讓View顯示出來
        mWM.addView(mView, mParams);
    }
}


// 隱藏
public void handleHide() {
    if (mView != null) {
        // note: checking parent() just to make sure the view has
        // been added...  i have seen cases where we get here when
        // the view isn't yet added, so let's try not to crash.
        if (mView.getParent() != null) {
            // 將View對象從窗體管理器移除
            mWM.removeView(mView);
        }

        mView = null;
    }
}

二、自定義Toast

通過以上的源碼分析,我想大家已經對Android系統中Toast的彈出和隱藏有了一定的理解,那麼知道了原理,我們也可以自己做一個吐司,還可以通知指定顯示的布局文件還做各種不同樣式的吐司。

1. 自定義吐司效果圖

明白了原理,我們可以專門做一個自定義Toast的工具類,這個類負責彈出吐司和隱藏吐司。然後在MainActivity中調用,這樣點擊按鈕就能彈出和隱藏吐司了。除此之外,Demo裡還做了雙擊吐司居中,三擊吐司隱藏的點擊事件邏輯,大家可以學習一下。

2. 分析工具類的代碼實現

在Demo的Toast工具類中,我們只需要像源代碼中一樣,在CustomToastUtil的構造方法中初始化LayoutParams,然後再寫彈出Toast的方法,隱藏吐司的方法就可以了。

工具類構造方法的實現,同源碼一樣,我們這裡也是初始化窗體的一些基本參數,並且初始化吐司要顯示的布局

   /**
     * 構造
     * @param context
     */
    public CustomToastUtil(Context context) {
        this.mContext = context;

        initParams();
    }

    /**
     * 初始化窗體屬性
     */
    private void initParams() {
        mWm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

        mParams = new WindowManager.LayoutParams();

        mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 類型
        mParams.type = WindowManager.LayoutParams.TYPE_PHONE;

        // 透明,不透明會出現重疊效果
        mParams.format = PixelFormat.TRANSLUCENT;

        // 位置屬性
        mParams.gravity = Gravity.TOP + Gravity.LEFT;  // 左上

        // 進來的時候把存儲的位置讀取顯示出來
        mParams.x = PreferenceUtil.getInt(mContext, "lastX");
        mParams.y = PreferenceUtil.getInt(mContext, "lastY");

        // 初始化吐司窗口布局
        mView = View.inflate(mContext, R.layout.view_toast, null);
    }

接下來就是顯示吐司的邏輯,其實特別簡單,核心代碼只有一句,這裡要先獲取自定義填充布局文件中的TextView顯示文本顯示你要彈出的文本,然後對View的點擊進行了監聽,實現了雙擊將吐司居中顯示,和三擊隱藏吐司的功能。

/**
 * 彈出自定義吐司
 */
public void popToast(String text) {
    TextView tvName = (TextView) mView.findViewById(R.id.tv_toast_name);
    // 設置顯示的文字
    tvName.setText(text);

    // 吐司窗體的背景可以在布局文件之中指定也可以在代碼中設置


    // 設置吐司的雙擊事件,點擊之後會到中心點
    mView.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            // 雙擊事件處理邏輯
            System.arraycopy(mHits1, 1, mHits1, 0, mHits1.length - 1);
            mHits1[mHits1.length - 1] = SystemClock.uptimeMillis();
            if (mHits1[0] >= (SystemClock.uptimeMillis() - 500)) {
                // 雙擊之後執行
                // 讓吐司移動到x中心,y不需要對中
                // 更新窗體的坐標
                mParams.x = (mWm.getDefaultDisplay().getWidth() - mView.getWidth()) / 2;
                mWm.updateViewLayout(mView, mParams);

                // 點擊完退出的時候也把位置信息存儲起來
                PreferenceUtil.putInt(mContext, "lastX", mParams.x);
                PreferenceUtil.putInt(mContext, "lastY", mParams.y);
            }


            // 三擊事件處理邏輯
            System.arraycopy(mHits2, 1, mHits2, 0, mHits2.length - 1);
            mHits2[mHits2.length - 1] = SystemClock.uptimeMillis();
            if (mHits2[0] >= (SystemClock.uptimeMillis() - 600)) {
                // 點擊之後將吐司移除掉
                if (mView != null) {
                    if (mView.getParent() != null) {
                        mWm.removeView(mView);
                    }
                }
            }
        }
    });


    // 設置吐司的觸摸滑動事件
    mView.setOnTouchListener(new View.OnTouchListener() {

        int startX;
        int startY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: // 按下

                    // 手指按下時的坐標位置
                    startX = (int) event.getRawX();
                    startY = (int) event.getRawY();

                    break;
                case MotionEvent.ACTION_MOVE: // 移動

                    // 移動後的坐標位置
                    int newX = (int) event.getRawX();
                    int newY = (int) event.getRawY();

                    // 偏移量
                    int dx = newX - startX;
                    int dy = newY - startY;

                    // 給偏移量設置邊距
                    // 小於x軸
                    if (mParams.x < 0) {
                        mParams.x = 0;
                    }
                    // 小於y軸
                    if (mParams.y < 0) {
                        mParams.y = 0;
                    }

                    // 超出x軸
                    if (mParams.x > mWm.getDefaultDisplay().getWidth() - mView.getWidth()) {
                        mParams.x = mWm.getDefaultDisplay().getWidth() - mView.getWidth();
                    }
                    // 超出y軸
                    if (mParams.y > mWm.getDefaultDisplay().getHeight() - mView.getHeight()) {
                        mParams.y = mWm.getDefaultDisplay().getHeight() - mView.getHeight();
                    }

                    // 更新窗體的坐標
                    mParams.x += dx;
                    mParams.y += dy;
                    mWm.updateViewLayout(mView, mParams);

                    // 重新賦值起始坐標
                    startX = (int) event.getRawX();
                    startY = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_UP: // 抬起
                    // 抬起來的時候保存最後一次的位置,下次進來時直接顯示出來
                    PreferenceUtil.putInt(mContext, "lastX", mParams.x);
                    PreferenceUtil.putInt(mContext, "lastX", mParams.y);
                    break;
                default:
                    break;
            }

            return false;  // 當有父控件有點擊事件時,這裡要返回false,不然父控件就拿不到點擊事件了
        }
    });

    if (mView != null) {
        if (mView.getParent() != null) {
            mWm.removeView(mView);
        }
    }
    // 添加到窗體管理器中才能顯示出來
    mWm.addView(mView, mParams);
}

最後是吐司的隱藏方法,這個方法只需要將View從窗體管理器中移除掉就好了。

    /**
     * 從父窗體中移除吐司
     */
    public void hideToast() {
        if (mView != null) {
            if (mView.getParent() != null) {
                mWm.removeView(mView);
            }
        }
    }

以上代碼簡單的實現了自定義Toast的顯示和隱藏,當然你也可以給顯示的Toast加一個背景,這樣吐司看起來就更加的漂亮了。以上就是本次分享的全部內容,謝謝大家。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved