不得不吐槽的Android PopupWindow的幾個痛點

cj0619 8年前發布 | 40K 次閱讀 Android開發 移動開發 PopupWindow

說到PopupWindow,我個人感覺是又愛又恨,沒有深入使用之前總覺得這個東西應該很簡單,很好用,但是真正使用PopupWindow實現一些效果的時候總會遇到一些問題,但是即便是人家的api有問題,作為程序員也沒有辦法,只能去想辦法去補救。

下面是我在使用過程中發現的關于PopupWindow的幾個痛點:

痛點一: 不設置背景就不能響應返回鍵和點擊外部消失的,這個我認為就是api留下的bug,有些版本里面修復了這個問題,感興趣的可以多看看幾個版本的源碼,還可以看出Google是怎么修改的。

痛點二: showAsDropDown(View anchorView)方法使用也會遇到坑,如果不看api注釋,會認為PopupWindow只能顯示在anchorView的下面(與anchorView左下角對齊顯示),但是看了方法注釋之后發現次方法是可以讓PopupWindow顯示在anchorView的上面的(anchorView左上角對齊顯示)。如果真這樣,那實現自適應帶箭頭的上下文菜單不就很容易了么,事實證明還是會有些瑕疵。

痛點三: 個人覺得api設計得不好使,不過這個只能怪自己對api理解不夠深刻,不過下面幾個api組合使用還是得介紹一下。

// 如果不設置PopupWindow的背景,有些版本就會出現一個問題:無論是點擊外部區域還是Back鍵都無法dismiss彈框
popupWindow.setBackgroundDrawable(new ColorDrawable());

// setOutsideTouchable設置生效的前提是setTouchable(true)和setFocusable(false)
popupWindow.setOutsideTouchable(true);

// 設置為true之后,PopupWindow內容區域 才可以響應點擊事件
popupWindow.setTouchable(true);

// true時,點擊返回鍵先消失 PopupWindow
// 但是設置為true時setOutsideTouchable,setTouchable方法就失效了(點擊外部不消失,內容區域也不響應事件)
// false時PopupWindow不處理返回鍵
popupWindow.setFocusable(false);
popupWindow.setTouchInterceptor(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return false;   // 這里面攔截不到返回鍵
    }
});

將理論始終聽起來很形象,通過實例可以讓人更加印象深刻,第一點已經有文章介紹了,下面實現一個帶箭頭的上下文菜單體會一下痛點二和三,到底怎么個痛法。先上效果再上代碼,代碼里面的注釋標注了痛點的地方。

上下文菜單效果圖

默認向下彈出

下面空間不足時先上彈出

特例出現了,我希望第一排右邊按鈕點擊時PopupWindow在下面,但是我失望了

雖然達不到我要的效果,但是作為學習資源還是不錯的,下面貼出代碼

import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.Toast;

public class TopBottomArrowPopupActivity extends Activity implements View.OnClickListener {

    private View mButton1;
    private View mButton2;
    private View mButton3;
    private View mButton4;
    private View mButton5;
    private View mButton6;
    private PopupWindow mCurPopupWindow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_top_arrow_pos_window);
        mButton1 = findViewById(R.id.buttion1);
        mButton2 = findViewById(R.id.buttion2);
        mButton3 = findViewById(R.id.buttion3);
        mButton4 = findViewById(R.id.buttion4);
        mButton5 = findViewById(R.id.buttion5);
        mButton6 = findViewById(R.id.buttion6);
        mButton1.setOnClickListener(this);
        mButton2.setOnClickListener(this);
        mButton3.setOnClickListener(this);
        mButton4.setOnClickListener(this);
        mButton5.setOnClickListener(this);
        mButton6.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.buttion1:
                mCurPopupWindow = showTipPopupWindow(mButton1, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case R.id.buttion2:
                mCurPopupWindow = showTipPopupWindow(mButton2, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case R.id.buttion3:
                mCurPopupWindow = showTipPopupWindow(mButton3, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case R.id.buttion4:
                mCurPopupWindow = showTipPopupWindow(mButton4, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case R.id.buttion5:
                showTipPopupWindow(mButton5, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case R.id.buttion6:
                mCurPopupWindow = showTipPopupWindow(mButton6, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
        }
    }

    public PopupWindow showTipPopupWindow(final View anchorView, final View.OnClickListener onClickListener) {
        final View contentView = LayoutInflater.from(anchorView.getContext())
                                                                        .inflate(R.layout.popuw_content_top_arrow_layout, null);
        contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        // 創建PopupWindow時候指定高寬時showAsDropDown能夠自適應
        // 如果設置為wrap_content,showAsDropDown會認為下面空間一直很充足(我以認為這個Google的bug)
        // 備注如果PopupWindow里面有ListView,ScrollView時,一定要動態設置PopupWindow的大小
        final PopupWindow popupWindow = new PopupWindow(contentView,
                contentView.getMeasuredWidth(), contentView.getMeasuredHeight(), false);

        contentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                popupWindow.dismiss();
                onClickListener.onClick(v);
            }
        });

        contentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // 自動調整箭頭的位置
                autoAdjustArrowPos(popupWindow, contentView, anchorView);
                contentView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
        // 如果不設置PopupWindow的背景,有些版本就會出現一個問題:無論是點擊外部區域還是Back鍵都無法dismiss彈框
        popupWindow.setBackgroundDrawable(new ColorDrawable());

        // setOutsideTouchable設置生效的前提是setTouchable(true)和setFocusable(false)
        popupWindow.setOutsideTouchable(true);

        // 設置為true之后,PopupWindow內容區域 才可以響應點擊事件
        popupWindow.setTouchable(true);

        // true時,點擊返回鍵先消失 PopupWindow
        // 但是設置為true時setOutsideTouchable,setTouchable方法就失效了(點擊外部不消失,內容區域也不響應事件)
        // false時PopupWindow不處理返回鍵
        popupWindow.setFocusable(false);
        popupWindow.setTouchInterceptor(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;   // 這里面攔截不到返回鍵
            }
        });
        // 如果希望showAsDropDown方法能夠在下面空間不足時自動在anchorView的上面彈出
        // 必須在創建PopupWindow的時候指定高度,不能用wrap_content
        popupWindow.showAsDropDown(anchorView);
        return popupWindow;
    }

    private void autoAdjustArrowPos(PopupWindow popupWindow, View contentView, View anchorView) {
        View upArrow = contentView.findViewById(R.id.up_arrow);
        View downArrow = contentView.findViewById(R.id.down_arrow);

        int pos[] = new int[2];
        contentView.getLocationOnScreen(pos);
        int popLeftPos = pos[0];
        anchorView.getLocationOnScreen(pos);
        int anchorLeftPos = pos[0];
        int arrowLeftMargin = anchorLeftPos - popLeftPos + anchorView.getWidth() / 2 - upArrow.getWidth() / 2;
        upArrow.setVisibility(popupWindow.isAboveAnchor() ? View.INVISIBLE : View.VISIBLE);
        downArrow.setVisibility(popupWindow.isAboveAnchor() ? View.VISIBLE : View.INVISIBLE);

        RelativeLayout.LayoutParams upArrowParams = (RelativeLayout.LayoutParams) upArrow.getLayoutParams();
        upArrowParams.leftMargin = arrowLeftMargin;
        RelativeLayout.LayoutParams downArrowParams = (RelativeLayout.LayoutParams) downArrow.getLayoutParams();
        downArrowParams.leftMargin = arrowLeftMargin;
    }

    @Override
    public void onBackPressed() {
        if (mCurPopupWindow != null && mCurPopupWindow.isShowing()) {
            mCurPopupWindow.dismiss();
        } else {
            super.onBackPressed();
        }
    }
}

結束語

雖然不能完全把PopupWindow的問題描述清楚,但是只要知道有這些坑,以后寫代碼的時候就會多留意下,知道PopupWindow的那幾個常用api相互組合會出現什么樣的結果。堅持寫文章不容易,但是感覺遇到的問題就應該記錄下來,好記性不如爛筆頭,時間長了可以通過文章記錄的知識快速為自己找到問題的解決方法。

 

來自:http://www.cnblogs.com/popfisher/p/5944054.html

 

 本文由用戶 cj0619 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!