Android开发PopupWindow弹出窗组件,可自适应输入法高度

855 阅读4分钟

需求分析:

当我们点击输入框时,会调出输入法软键盘,如果不做处理,PopupWindow评论窗口可能会挤到屏幕最上方,更糟糕的事件是看不到我们的输入框,连自己输入什么内容都看不到,这样用户体验非常差!下面先让大家看我们做出来的效果图:

1、上图可以看出,输入法弹出和隐藏,对于我们的评论窗口而言,高度始终都是保持不变,这样可以给用户带来更好的体验!

2、我们默认设置PopupWindow弹出窗的高度为屏幕的80%,您也可以在代码中设置其他比例。

二、首先,我们重写了RelativeLayout根布局,用来监听输入法状态

KeyboardLayout.java

package com.t20.commentdemo.view;

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.RelativeLayout;

//带有键盘监听的RelativeLayout
public class KeyboardLayout extends RelativeLayout {

	private KeyboardLayoutListener mListener;
	private boolean mIsKeyboardActive = false; //  输入法是否激活
	private int mKeyboardHeight = 0; // 输入法高度
	private Context mContext;

	public KeyboardLayout(Context context) {
		this(context, null, 0);
	}

	public KeyboardLayout(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		mContext = context;
		// 监听布局变化
		getViewTreeObserver().addOnGlobalLayoutListener(
				new KeyboardOnGlobalChangeListener());
	}

	private class KeyboardOnGlobalChangeListener implements
			OnGlobalLayoutListener {

		int mScreenHeight = 0;

		private int getScreenHeight() {
			if (mScreenHeight > 0) {
				return mScreenHeight;
			}
			mScreenHeight = ((Activity) mContext).getWindowManager()
					.getDefaultDisplay().getHeight();
			return mScreenHeight;
		}

		@Override
		// 视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时调用
		public void onGlobalLayout() {
			Rect rect = new Rect();
			// 获取当前页面窗口的显示范围
			((Activity) getContext()).getWindow().getDecorView()
					.getWindowVisibleDisplayFrame(rect);
			int screenHeight = getScreenHeight();
			int keyboardHeight = screenHeight - rect.bottom; // 输入法的高度
			boolean isActive = false;
			if (Math.abs(keyboardHeight) > screenHeight / 5) {
				isActive = true; // 超过屏幕五分之一则表示弹出了输入法
				mKeyboardHeight = keyboardHeight;
			}
			mIsKeyboardActive = isActive;
			if (mListener != null) {
				mListener.onKeyboardStateChanged(isActive, keyboardHeight);
			}
		}
	}

	public void setKeyboardListener(KeyboardLayoutListener listener) {
		mListener = listener;
	}

	public KeyboardLayoutListener getKeyboardListener() {
		return mListener;
	}

	public boolean isKeyboardActive() {
		return mIsKeyboardActive;
	}

	/**
	 * 获取输入法高度
	 * 
	 * @return
	 */
	public int getKeyboardHeight() {
		return mKeyboardHeight;
	}

	public interface KeyboardLayoutListener {
		/**
		 * @param isActive
		 *            输入法是否激活
		 * @param keyboardHeight
		 *            输入法面板高度
		 */
		void onKeyboardStateChanged(boolean isActive, int keyboardHeight);
	}

}

二、其次,我们自定义了一个PopupWindow,方便以后直接引用到其他项目中

CommentPopupWindow.java

package com.t20.commentdemo.view;

import com.t20.commentdemo.R;
import com.t20.commentdemo.view.KeyboardLayout.KeyboardLayoutListener;

import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class CommentPopupWindow extends PopupWindow {
	private Context mContext;
	private OnClickListener mOnClickListener;
	private OnFocusChangeListener mOnFocusChangeListener;

	private View mPopupWindowView;
	// popupWindow对应的布局
	private RelativeLayout mPopupWindowViewLayout;
	// 整个屏幕的宽度
	private int mScreenWidth;
	// 整个屏幕的高度
	private int mScreenHeight;
	// popupWindow对应的布局高度占屏幕的比例
	private final double mPopupWindowHeightForScreenPercent = 0.8;
	private LinearLayout mCommentPopupWindowHead;
	private LinearLayout mCommentPopupWindowFoot;
	//评论总数
	private TextView mTextViewCommentCount;
	//关闭窗口
	private TextView mTextViewClose;
	//评论的列表视图
	private ListView mListViewComments;
	//输入的评论内容
	private EditText mEditTextInput;
	//发送按钮
	private TextView mTextViewSend;

	// 用来给控件设置宽高
	private android.widget.LinearLayout.LayoutParams mLayoutParams;
	
	public EditText getmEditTextInput() {
		return mEditTextInput;
	}

	public void setmEditTextInput(EditText mEditTextInput) {
		this.mEditTextInput = mEditTextInput;
	}

	public CommentPopupWindow(Context context,
			OnClickListener onClickListener,
			OnFocusChangeListener onFocusChangeListener) {
		super(context);
		mContext = context;
		mOnClickListener = onClickListener;
		mOnFocusChangeListener = onFocusChangeListener;
		initDate();
		initView();
		setPopupWindowSize();
		initEvent();
	}

	private void initDate() {
		// 获取屏幕宽度和高度(以下注释的获取方式不能在该类构造里不然运行报错,所以换了种方式获取)
		DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
		mScreenWidth = dm.widthPixels;
		mScreenHeight = dm.heightPixels;
	}

	private void initView() {
		//获取布局
		mPopupWindowView = View.inflate(mContext, R.layout.comment_dialog, null);
		//获取控件
		mPopupWindowViewLayout = (RelativeLayout) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_layout);
		mCommentPopupWindowHead = (LinearLayout) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_head);
		mCommentPopupWindowFoot = (LinearLayout) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_foot);
		mTextViewCommentCount = (TextView) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_tv_commentsCount);
		mTextViewClose = (TextView) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_tv_close);
		mListViewComments = (ListView) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_lv);
		mEditTextInput = (EditText) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_et_input);
		mTextViewSend = (TextView) mPopupWindowView.findViewById(R.id.commentPopupWindow_comment_tv_send);
		mLayoutParams = (android.widget.LinearLayout.LayoutParams) mPopupWindowViewLayout.getLayoutParams();
	}

	private void setPopupWindowSize() {
		// 给ListView加上下外边距——注意BUG:最大布局不能是相对布局
		mCommentPopupWindowHead.measure(0, 0);
		int headHeight = mCommentPopupWindowHead.getMeasuredHeight();// 获得头部高度
		mCommentPopupWindowFoot.measure(0, 0);
		int footHeight = mCommentPopupWindowFoot.getMeasuredHeight();// 获得尾部高度
		mListViewComments.setPadding(0, headHeight, 0, footHeight);
		this.setContentView(mPopupWindowView);
		// 窗口外也能点击(点击区域外可以关闭该窗口)
		this.setOutsideTouchable(true);
		// 窗体可点击
		this.setFocusable(true);
		// 解决低版本android的bug(点击"返回Back"也能使其消失,并且不会影响你的背景)
		this.setBackgroundDrawable(new BitmapDrawable());
		// 设置内容的布局的高度(占屏幕高度的百分比)
		mLayoutParams.height = (int) (mScreenHeight * mPopupWindowHeightForScreenPercent);
		mPopupWindowViewLayout.setLayoutParams(mLayoutParams);
		// 设置popupwindow宽度和屏幕宽度一致
		this.setWidth(mScreenWidth);
		// 设popupwindow高度与内容一样高
		this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
		// 这句话,让pop覆盖在输入法上面
		this.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
		// 这句话,让pop自适应输入状态
		this.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
	}

	private void initEvent() {
		mTextViewClose.setOnClickListener(mOnClickListener);
		mEditTextInput.setOnFocusChangeListener(mOnFocusChangeListener);
		mTextViewSend.setOnClickListener(mOnClickListener);
	}
	
	/**
	 * 根据输入法键盘是否弹出,来改变评论popupWindow的布局高度
	 * @param keyboardLayout
	 */
	public void setPopupWindowFroKeyboard(KeyboardLayout keyboardLayout ){
		if (keyboardLayout != null) {
			keyboardLayout.setKeyboardListener(new KeyboardLayoutListener() {

				@Override
				public void onKeyboardStateChanged(boolean isActive,int keyboardHeight) {
					if (isActive) { // 输入法显示时
						setpopupWindowHeight(keyboardHeight);
					} else {// 输入法隐藏时
						setpopupWindowHeight(0);
					}
				}
			});
		}
	}
	// 设置评论窗口高度
	public void setpopupWindowHeight(int keyboardHeight) {
		mLayoutParams.height = (int) (mScreenHeight * mPopupWindowHeightForScreenPercent)- keyboardHeight;
		mPopupWindowViewLayout.setLayoutParams(mLayoutParams);
	}
}

三、我们的activity_main.xml布局,com.t20.commentdemo.view.KeyboardLayout是KeyboardLayout.java的全路径

<com.t20.commentdemo.view.KeyboardLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/keyboardLayout_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:onClick="openCommentWindow"
        android:textColor="#fff"
        android:text="点击弹出评论窗口" />

</com.t20.commentdemo.view.KeyboardLayout>

四、最后是活动MainActivity.java里的CommentPopupWindow弹出窗调用

package com.t20.commentdemo;

import com.t20.commentdemo.view.CommentPopupWindow;
import com.t20.commentdemo.view.KeyboardLayout;

import android.os.Bundle;
import android.app.Activity;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;

public class MainActivity extends Activity {
	// 最大布局KeyboardLayout(RelativeLayout改写)
	private KeyboardLayout mKeyboardLayoutRoot;
	// 评论
	private CommentPopupWindow mCommentPopupWindow;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// 1、隐藏标题栏,在加载布局之前设置(兼容Android2.3.3版本)
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		// 2、隐藏状态栏
		//getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);								
		setContentView(R.layout.activity_main);
		//获取控件
		mKeyboardLayoutRoot=(KeyboardLayout) findViewById(R.id.keyboardLayout_root);
	}

	/**
	 * 点击弹出评论窗口事件
	 * @param view
	 */
	public void openCommentWindow(View view) {
		//定义弹出窗
		mCommentPopupWindow=new CommentPopupWindow(MainActivity.this, onClickListener, onFocusChangeListener);
		// 显示PopupWindow
		mCommentPopupWindow.showAtLocation(mKeyboardLayoutRoot, Gravity.BOTTOM,0, 0);
		// 让输入框失去焦点
		mCommentPopupWindow.getmEditTextInput().clearFocus();	
		// 根据输入法键盘是否弹出,来改变评论popupWindow的布局高度
		mCommentPopupWindow.setPopupWindowFroKeyboard(mKeyboardLayoutRoot);
	}
	/**
	 * 监听评论中的点击事件
	 */
	private OnClickListener onClickListener=new OnClickListener() {
		
		@Override
		public void onClick(View view) {
			// TODO Auto-generated method stub
			switch (view.getId()) {
			// 关闭按钮
			case R.id.commentPopupWindow_comment_tv_close:
				mCommentPopupWindow.dismiss();
				break;
			// 点击发表评论
			case R.id.commentPopupWindow_comment_tv_send:
				break;
			}
		}
	};
	/**
	 * 监听评论中的焦点改变事件
	 */
	private OnFocusChangeListener onFocusChangeListener=new OnFocusChangeListener() {
		
		@Override
		public void onFocusChange(View view, boolean hasFocus) {
			// TODO Auto-generated method stub
			switch (view.getId()) {
			case R.id.commentPopupWindow_comment_et_input:
				// 此处为得到焦点时的处理内容
				if (hasFocus) { 
					// 聚焦时,判断用户是否登录,没登录可以跳转到登录界面					
				}
				break;
			}
		}
	};
}