Android开发之浅仿QQ聊天UI和键盘控制

话不多说,献上UI效果图
Android开发之浅仿QQ聊天UI和键盘控制_第1张图片
Android开发之浅仿QQ聊天UI和键盘控制_第2张图片
Android开发之浅仿QQ聊天UI和键盘控制_第3张图片
这里面设计到的知识点不多,先来了解系统输入法弹出方式.当输入内容时输入框被系统键盘遮挡了,影响了用户操作体验,这就是开发中非常常见的软键盘遮挡的问题,该如何解决?

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

这个方法适用于一般的滚动视图,会把视图往上平移,该方法等同于在Manifest里面给activity设置android:windowSoftInputMode,这里的枚举windowSoftInputMode参数具体含义不贴了,如有疑问请参照官方API,引起布局变化的是键盘的显示和隐藏,导致了根布局的onSizeChange变化,measure的重新测量布局。下面提供一个类控制键盘:

import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;

public class InputHelper {

    private static InputHelper instance;
    private Context mContext;

    public InputHelper(Context mContext){
        this.mContext=mContext;
    }

    public static InputHelper getInstance(Context mContext){
        if(instance==null){
            synchronized (InputHelper.class) {
                if(instance==null){
                    instance=new InputHelper(mContext);
                }
            }
        }
        return instance;
    }

    /**
     * 显示键盘
     * @param view
     */
    public void showKeyboard(View view){
        InputMethodManager manager=(InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);  
        manager.showSoftInput(view, InputMethodManager.SHOW_FORCED);  
    }

    /**
     * 隐藏键盘
     * @param view
     */
    public void hideKeyboard(View view){
        InputMethodManager manager = ( InputMethodManager )mContext.getSystemService( Context.INPUT_METHOD_SERVICE );     
        if ( manager.isActive( ) ) {     
            manager.hideSoftInputFromWindow( view.getApplicationWindowToken( ) , 0 );   
        }    
    }
}

类似上图效果的UI,弹出键盘按照上述方法是不管用的,这里需要重新计算键盘的高度,让对应的根视图向上移动给键盘腾出空间,这里需要用到类ViewTreeObserver。

public final class ViewTreeObserver {
/**
     * Interface definition for a callback to be invoked when the focus state within
     * the view tree changes.
     */
    public interface OnGlobalFocusChangeListener {
        /**
         * Callback method to be invoked when the focus changes in the view tree. When
         * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
         * When the view tree transitions from non-touch mode to touch mode, newFocus is
         * null. When focus changes in non-touch mode (without transition from or to
         * touch mode) either oldFocus or newFocus can be null.
         *
         * @param oldFocus The previously focused view, if any.
         * @param newFocus The newly focused View, if any.
         */
        public void onGlobalFocusChanged(View oldFocus, View newFocus);
    }

    /**
     * Interface definition for a callback to be invoked when the global layout state
     * or the visibility of views within the view tree changes.
     */
    public interface OnGlobalLayoutListener {
        /**
         * Callback method to be invoked when the global layout state or the visibility of views
         * within the view tree changes
         */
        public void onGlobalLayout();
    }
    //...................此处略..........................
}

这是一个注册监听视图树的观察者(observer),在视图树种全局事件改变时得到通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等。ViewTreeObserver不能够被应用程序实例化,因为它是由视图提供,该类在View内部可获取实例:

/**
     * Returns the ViewTreeObserver for this view's hierarchy. The view tree
     * observer can be used to get notifications when global events, like
     * layout, happen.
     *
     * The returned ViewTreeObserver observer is not guaranteed to remain
     * valid for the lifetime of this View. If the caller of this method keeps
     * a long-lived reference to ViewTreeObserver, it should always check for
     * the return value of {@link ViewTreeObserver#isAlive()}.
     *
     * @return The ViewTreeObserver for this view's hierarchy.
     */
    public ViewTreeObserver getViewTreeObserver() {
        if (mAttachInfo != null) {
            return mAttachInfo.mTreeObserver;
        }
        if (mFloatingTreeObserver == null) {
            mFloatingTreeObserver = new ViewTreeObserver();
        }
        return mFloatingTreeObserver;
    }

通过获取观察者的实例,设置相应的监听,这里我们需要的是ViewTreeObserver.OnGlobalLayoutListener,当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,调用该接口相应的回调函数,在我们的代码调用如下:

/**
     * 调整键盘视图
     * @param root 
     * @param bottomView 
     */
    private void onKeyboardLayout(final View root, final View bottomView) {
        root.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                root.getWindowVisibleDisplayFrame(rect);
                int rootInvisibleHeight = root.getRootView().getHeight() - rect.bottom;
                if (rootInvisibleHeight > 100) {
                    int[] location = new int[2];
                    bottomView.getLocationInWindow(location);
                    int srollHeight = (location[1] + bottomView.getHeight()) - rect.bottom;
                    root.scrollTo(0, srollHeight);
                } else {
                    root.scrollTo(0, 0);
                }
            }
        });
    }

下面一个测试类,根据QQ聊天的UI修改而成,但是底部ViewPager没有具体实现,这不是本篇重点


import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;

public class MainActivity extends Activity implements OnClickListener{

    private RadioGroup radioGroup;
    private LinearLayout root;
    private ListView listView;
    private EditText chat;
    private LinearLayout footer;
    private ViewPager viewPager;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
        radioGroup=(RadioGroup)findViewById(R.id.radioGroup);
        root=(LinearLayout)findViewById(R.id.root);
        footer=(LinearLayout)findViewById(R.id.footer);
        chat=(EditText)findViewById(R.id.EditText_input_chat);
        listView=(ListView)findViewById(R.id.ListView_body);
        viewPager=(ViewPager)findViewById(R.id.ViewPager_chat);
        onKeyboardLayout(root, footer);
        listView.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                InputHelper.getInstance(getApplicationContext()).hideKeyboard(chat);
                if(viewPager.getVisibility()==View.VISIBLE){
                    viewPager.setVisibility(View.GONE);
                    RadioButton checkButton=(RadioButton)findViewById(radioGroup.getCheckedRadioButtonId());
                    checkButton.setChecked(false);
                }

                return false;
            }
        });

        for(int i=0;ithis);
        }

        radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                for(int i=0;iif(radioGroup.getChildAt(i).getId()!=checkedId){
                        radioGroup.getChildAt(i).setTag(0);
                    }
                }
            }
        });
    }

    /**
     * 调整键盘视图
     * @param root 
     * @param bottomView 
     */
    private void onKeyboardLayout(final View root, final View bottomView) {
        root.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                root.getWindowVisibleDisplayFrame(rect);
                int rootInvisibleHeight = root.getRootView().getHeight() - rect.bottom;
                if (rootInvisibleHeight > 100) {
                    int[] location = new int[2];
                    bottomView.getLocationInWindow(location);
                    int srollHeight = (location[1] + bottomView.getHeight()) - rect.bottom;
                    root.scrollTo(0, srollHeight);
                } else {
                    root.scrollTo(0, 0);
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        InputHelper.getInstance(getApplicationContext()).hideKeyboard(chat);
        if(Integer.valueOf(v.getTag().toString())==0){
            RadioButton mClickButton=(RadioButton) v;
            v.setTag(1);
            if(viewPager.getVisibility()!=View.VISIBLE){
                viewPager.setVisibility(View.VISIBLE);
                mClickButton.setChecked(true);
                radioGroup.check(v.getId());
            }
            //setCurrentPage()
        }else if(Integer.valueOf(v.getTag().toString())==1){
            v.setTag(0);
            RadioButton mClickButton=(RadioButton) v;
            mClickButton.setChecked(false);
            viewPager.setVisibility(View.GONE);
            radioGroup.check(-1);
        }
   }
}

看着这一堆乱糟糟的代码,我知道我应该重构了,这里只提思路不贴代码了,ListView的onTouch事件可以放到一个自定义的ListView里面执行,同时提供一个回调函数用于控制其它视图(例如ViewPager),RadioGroup这里的onClick过于累赘了,可以抽离成方法,代码看着就简洁多了,具体做法就不写了这里,如果你有兴趣可以慢慢的尝试着重构,逗比要看书去了(源码下载地址,特别说明源码类里没有控制EditText的focuse变化改变视图,逗比我特么太懒了不想写了,你如果需要就自己写吧)

你可能感兴趣的:(Android)