JKeyboardPanelSwitch源码学习

前言

最近准备做个输入法表情语音合在一起的输入控件,看到了网上有JKeyboardPanelSwitch(地址),就拉下来学习了下。JKeyboardPanelSwitch可以保证在输入法键盘和其他键盘之间切换不抖动,效果非常好,而且解决了很多适配问题。

本文对此代码进行学习。

实现好之后,输入法键盘也会使用这个区域,实现了自定义键盘和输入法键盘公用一块区域,使得切换起来不抖动,输入条的位置不变化。具体效果如下所示,图来自github。



定义



首先定义几个部分,编辑框所在这行为bar,照片位置这块为panel,输入法的键盘我们称为keyboard,如上图所示。实际上,最核心的问题就是panelkeyboard可以随意切换,而保持bar的位置不变

要解决这个问题,我们首先得有点基础知识。

1、怎么知道输入法的弹起和收缩

一般来说输入法的弹起与收缩,我们是不知道的,在“输入法键盘和编辑框焦点”这篇文章我们曾经说过,windowSoftInputMode为adjustResize,当输入法弹起或者收缩的时候会调用根布局的onSizeChanged方法,可以通过这个方法来监控输入法。而onSizeChanged是在的layout()内的setFrame()内的sizeChange()内调用的,这是layout的前期,在onLayout之前,能不能提前知道输入法被调起呢?当然可以,onLayout之前必定有onMeasure,我们可以在onMeasure里监控高度的变化从而知道输入法是否弹起。

 public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

简单的说,我们可以设置windowSoftInputMode为adjustResize,通过监控跟布局的onMeasure方法,来知道输入法的弹起与收缩。

这在大部分情况是OK的,但也有失效的时候,后文会补充。


2、怎么保证panel和keyboard一样大小?

keyboard的高度是由输入法app决定的,panel高度是我们在代码里可以设置的。我们刚才说了onMeasure里可以监控到keyboard,那么keyboard弹起,必然导致整个布局收缩,收缩多少可以在onMeasure里得到数据,收缩的值就是输入法的高度,这样我们就得到了输入法的高度,我们可以把这个值存起来(存到文件或db或sharepreference),并且设置给panel。

3、分类

不同类型的activity对keyboard弹起有不同处理方法,所以根据状态栏是否透明,是否全屏,是否fitsSystemWindows将activity分类别,

非全屏主题 或者 透明状态栏主题并且在fitsSystemWindows=true 情况下参考ChattingResolvedActivity
全屏主题 或者 透明状态栏主题并且在 fitsSystemWindows=false 情况下参考ChattingResolvedHandleByPlaceholderActivity

ChattingResolvedActivity具体分析

以ChattingResolvedActivity作为例子来分析。

布局是这样子的,核心就是最外层用KPSwitchRootLinearLayout这个类,然后底部放一个KPSwitchPanelLinearLayout,这个布局里面可以放自定义的面板内容(比如表情,语音,附加内容),要保持这个布局和keyboard占据同样位置。

<?xml version="1.0" encoding="utf-8"?>
<cn.dreamtobe.kpswitch.widget.KPSwitchRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/view_message_list" />

    <include layout="@layout/layout_send_message_bar" />


    <cn.dreamtobe.kpswitch.widget.KPSwitchPanelLinearLayout
        android:id="@+id/panel_root"
        style="@style/Panel"
        android:visibility="gone">

        <include layout="@layout/merge_panel_content" />
    </cn.dreamtobe.kpswitch.widget.KPSwitchPanelLinearLayout>

</cn.dreamtobe.kpswitch.widget.KPSwitchRootLinearLayout>

类图


panel切换到keyboard

我们先来看下panel切换到keyboard的过程.这个过程需要完全2件事,隐藏panel,显示keyboard,但是keyboard的显示有个过程,所以会产生抖动,如何才能解决抖动呢?在keyboard真正要展示出来的时候,隐藏panel就可以。下面看看代码怎么实现的。

点击编辑框,可以导致keyboard弹出,导致重新布局,KPSwitchRootLinearLayout开始onMeasure

    //KPSwitchRootLinearLayout#onMeasure
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        conflictHandler.handleBeforeMeasure(MeasureSpec.getSize(widthMeasureSpec),
                MeasureSpec.getSize(heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
这个onMeasure在真正调用super.onMeasure(widthMeasureSpec, heightMeasureSpec);之前做了些处理,很关键。我们来看看handleBeforeMeasure干了什么,KPSwitchRootLayoutHandler这个类我改了一点,和github不太一致,后文会贴出此类源码。可以看到,输入法弹起,offset必然大于0,会调用            mPanelLayout.handleHide();实际上是把KPSwitchPanelLayoutHandler内的mIsHide 置为true;
 //KPSwitchRootLayoutHandler#handleBeforeMeasure
 public void handleBeforeMeasure(final int width, int height) {
        // 由当前布局被键盘挤压,获知,由于键盘的活动,导致布局将要发生变化。
        。。。
        final int offset = mOldHeight - height;

        if (offset == 0) {
            Log.d(TAG, "" + offset + " == 0 break;");
            return;
        }

        if (Math.abs(offset) == mStatusBarHeight) {
            Log.w(TAG, String.format("offset just equal statusBar height %d", offset));
            // 极有可能是 相对本页面的二级页面的主题是全屏&是透明,但是本页面不是全屏,因此会有status bar的布局变化差异,进行调过
            // 极有可能是 该布局采用了透明的背景(windowIsTranslucent=true),而背后的布局`full screen`为false,
            // 因此有可能第一次绘制时没有attach上status bar,而第二次status bar attach上去,导致了这个变化。
            return;
        }
        。。。

        // 检测到真正的 由于键盘收起触发了本次的布局变化

        if (offset > 0) {
            //键盘弹起 (offset > 0,高度变小)
            mPanelLayout.handleHide();
        } else if (mPanelLayout.isKeyboardShowing()) {
            // 1. 总得来说,在监听到键盘已经显示的前提下,键盘收回才是有效有意义的。
            // 2. 修复在Android L下使用V7.Theme.AppCompat主题,进入Activity,默认弹起面板bug,
            // 第2点的bug出现原因:在Android L下使用V7.Theme.AppCompat主题,并且不使用系统的ActionBar/ToolBar,V7.Theme.AppCompat主题,还是会先默认绘制一帧默认ActionBar,然后再将他去掉(略无语)
            //键盘收回 (offset < 0,高度变大)
            if (mPanelLayout.isVisible()) {
                // the panel is showing/will showing
                mPanelLayout.handleShow();
            }
        }
    }
搞了半天,只是把KPSwitchPanelLayoutHandler内的mIsHide 置为true;,就做了这么一点事。这有什么意义啊?注意此时panel并没有设置为gone。

我们接着看,此时panel是visiblle的,KPSwitchRootLinearLayout的onMeasure必然会导致panel被onMeasure,这的panel是KPSwitchPanelLinearLayout。看看他的measure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int[] processedMeasureWHSpec = panelLayoutHandler.processOnMeasure(widthMeasureSpec,
                heightMeasureSpec);

        super.onMeasure(processedMeasureWHSpec[0], processedMeasureWHSpec[1]);
    }
似曾相识啊,又搞了个handler,接着往下看,因为mIsHide被刚刚被设置为true,所以panel会变成gone,并且修改widthMeasureSpec和heightMeasureSpec的值,让他们的size为0,这样接着去measure panel的child,child就不会有宽高了,否则会有问题,虽然不显示(因为gone),但是还占着空间(因为measure的时候把高度算进去了),如下图所示。panel切键盘,作者巧妙的利用了一个view在measure时把自己置gone,不会触发requestLayout,来使得切换不闪。

    public int[] processOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mIsHide) {
            panelLayout.setVisibility(View.GONE);
            /**
             * The current frame will be visible nil.
             */
            //强行给上0,0,是为了让child在measure的时候得到结果都是0,否则会有问题,虽然不显示(因为gone),但是还占着空间,如下图所示
            widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
            heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
        }

        processedMeasureWHSpec[0] = widthMeasureSpec;
        processedMeasureWHSpec[1] = heightMeasureSpec;

        return processedMeasureWHSpec;
    }


keyboard切换到panel

再来看看keyboard切换到panel的。点击“加号”按钮,会导致keyboard切换到panel,代码如下,核心代码是switchPanelAndKeyboard

//cn.dreamtobe.kpswitch.util.KPSwitchConflictUtil#attach
@Override
public void onClick(View v) {
    final boolean switchToPanel = switchPanelAndKeyboard(panelLayout, focusView);
    if (switchClickListener != null) {
        switchClickListener.onClickSwitch(switchToPanel);
    }
}
看下边代码,此时panel为gone,所以switchToPanel为true,会走showPanel

    public static boolean switchPanelAndKeyboard(final View panelLayout, final View focusView) {
        boolean switchToPanel = panelLayout.getVisibility() != View.VISIBLE;
        if (!switchToPanel) {
            showKeyboard(panelLayout, focusView);
        } else {
            showPanel(panelLayout);
        }

        return switchToPanel;
    }
再看showPanel,这里很简单的做了2个事,panel显示,keyboard隐藏。啊??这不是会抖动的吗?没错,这么写是会抖动的,可是注意这里的setVisibility被重写了。
    public static void showPanel(final View panelLayout) {
        final Activity activity = (Activity) panelLayout.getContext();
        panelLayout.setVisibility(View.VISIBLE);
        if (activity.getCurrentFocus() != null) {
            KeyboardUtil.hideKeyboard(activity.getCurrentFocus());
        }
    }
从这里可以看到如果filterSetVisibility返回true了就根本不调用实际的setVisibility。

    @Override
    public void setVisibility(int visibility) {
        if (panelLayoutHandler.filterSetVisibility(visibility)) {
            return;
        }
        super.setVisibility(visibility);
    }
从下边可以看出此时满足isKeyboardShowing() && visibility == View.VISIBLE,所以必定返回true,这里把mIsHide设置为false然后返回true。

    public boolean filterSetVisibility(final int visibility) {
        if (visibility == View.VISIBLE) {
            this.mIsHide = false;
        }

        if (visibility == panelLayout.getVisibility()) {
            return true;
        }

        /**
         * For handling Keyboard->Panel.
         *
         * Will be handled on {@link KPSwitchRootLayoutHandler#handleBeforeMeasure(int, int)} ->
         * {@link IPanelConflictLayout#handleShow()} Delay show, until the {@link KPSwitchRootLayoutHandler} discover
         * the size is changed by keyboard-show. And will show, on the next frame of the above
         * change discovery.
         */
        if (isKeyboardShowing() && visibility == View.VISIBLE) {
            return true;
        }

        return false;
    }

好了,搞了半天,干了哪些事呢?此时调用了KeyboardUtil.hideKeyboard(activity.getCurrentFocus());,并且把mIsHide设置为false。这样子就能实现,输入法隐藏,panel展示吗?
KeyboardUtil.hideKeyboard(activity.getCurrentFocus())会导致输入法隐藏,导致cn.dreamtobe.kpswitch.handler.KPSwitchRootLayoutHandler#handleBeforeMeasure,这里会走这段代码
 if (mPanelLayout.isVisible()) {
     // the panel is showing/will showing
     mPanelLayout.handleShow();
 }
 注意,这里的isVisible也不是真正意义上的view是否显示,看代码,我们刚才把mIsHide设置为false,那isVisible()就会返回true。导致mPanelLayout.handleShow()被调用。在handleShow()内部真正把view设置为true。

    @Override
    public boolean isVisible() {
        return !mIsHide;
    }

小结

总的来说,就是 把panel的显示或者隐藏推迟到keyboard的隐藏或显示过程中,用了一个isHide变量来进行过渡。

ChattingResolvedHandleByPlaceholderActivity具体分析

我们在 怎么知道输入法的弹起和收缩 这里说过有失效的时候,什么时候会失效呢?activity全屏的时候会失效,ChattingResolvedHandleByPlaceholderActivity就是全屏的情况。在activity全屏的时候,如果弹起了输入法,不会改变跟布局的高度,所以无法在onMeasure处获取到输入法弹起收缩的信息,必须寻找另外的方法。ChattingResolvedHandleByPlaceholderActivity就展示了这种方法。
可以设置windowSoftInputMode为adjustUnspecified,然后监听onGlobalLayout方法,在回调里看根布局的 getWindowVisibleDisplayFrame,得到一个rect,这个rect的高度会因为输入法的弹起或收缩而改变。
设置adjustUnspecified与设置adjustpan效果一样,系统应该会自动转化为adjustpan。
我们先来看看布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <include layout="@layout/view_message_list" />
    <include layout="@layout/layout_send_message_bar" />

    <cn.dreamtobe.kpswitch.widget.KPSwitchFSPanelLinearLayout
        android:id="@+id/panel_root"
        style="@style/Panel"
        android:visibility="gone">

        <include layout="@layout/merge_panel_content" />
    </cn.dreamtobe.kpswitch.widget.KPSwitchFSPanelLinearLayout>

</LinearLayout>

刚才是利用一个根布局,一个底部布局,这里只用了KPSwitchFSPanelLinearLayout,简单了很多,也没有那么多复杂的改写OnMeasure方法了。
先回一下adjustpan是如何处理keyboard弹起的, http://blog.csdn.net/litefish/article/details/46313785#t3里有这么一段话
adjustPan主要是将原布局作为一个ScrollView,进行上移,这样就可以保证要输入的位置可以看到,不被遮住。看下图可以清楚看到上移,上移就是为了当前编辑框不被遮住.注意不是一定会上移的,如果在当前位置弹出键盘,不会挡住编辑框,那就直接弹出

点击编辑框

先看看点击编辑框,会弹出输入法,但是但是,和想象的不一样,按理说点击编辑框,弹起输入法的时候,整个布局会上移,为了给keyboard腾出空间,但是ChattingResolvedHandleByPlaceholderActivity并不会这样,那是因为下面这段代码。
点击编辑框,onTouch的时候把panel设置为INVISIBLE,并且由于onTouch返回false,所以会调用onTouchEvent会调起输入法。就2步,第一步panel设置为INVISIBLE,这样界面上看起来就是bar升高,这个时候实际上bar下面有了一个panel,只是他是INVISIBLE,所以我们看不到,然后,下一步,keyboard要出来了,我们再回头看看adjustpan的原理,此时keyboard出来不会挡住编辑框,所以不用上移了。我们要是把这里的INVISIBLE改为VISIBLE,就可以看到panel一闪而过了。巧妙的用了一个INVISIBLE的panel来避免了整个页面被上移,很赞。当然把输入法收起的时候,也要gone掉这个panel,这个代码由KPSwitchFSPanelLayoutHandler#onKeyboardShowing实现。
        //KPSwitchConflictUtil
        if (isHandleByPlaceholder(activity)) {
            focusView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_UP) {
                        /**
                         * Show the fake empty keyboard-same-height panel to fix the conflict when
                         * keyboard going to show.
                         * @see KPSwitchConflictUtil#showKeyboard(View, View)
                         */
                        panelLayout.setVisibility(View.INVISIBLE);
                    }
                    return false;
                }
            });
        }

panel切换到keyboard

这里的逻辑跟“点击编辑框”类似,就不说了。

keyboard切换到panel

此时panel会从INVISIBLE切换到VISIBLE,然后隐藏键盘
    //KPSwitchConflictUtil
    public static void showPanel(final View panelLayout) {
        final Activity activity = (Activity) panelLayout.getContext();
        panelLayout.setVisibility(View.VISIBLE);
        if (activity.getCurrentFocus() != null) {
            KeyboardUtil.hideKeyboard(activity.getCurrentFocus());
        }
    }

切换到home再切回来

我们可以看到在ChattingResolvedHandleByPlaceholderActivity里有这么一段代码,这是干嘛用的呢?
    @Override
    protected void onPause() {
        super.onPause();
        panelRoot.recordKeyboardStatus(getWindow());
    }

注释掉这个代码会发生什么问题?我们先试试全屏的情况下,我们可以明显的看到,最后一帧,整个页面都上挪了,标题都被顶走了。这是因为onPause前,bar还是focus的,但是keyboard不在了。onResume之后,bar是focus的,所以要弹起键盘,而此时键盘弹起并没有先让panel做INVISIBLE,所以就产生了这个问题。


换一个类型,我们看透明状态栏主题并且在 fitsSystemWindows=false 情况下,一样有坑,最后画面没有bar,但是keyboard出来了。顺便说一句,这个case下,原有代码也是会显示不正确的,后面我会改一下。


小结

相对来说,全屏的情况比较简单, 核心思想就是在输入法弹出之前把panel设置为INVISIBLE,避免整个页面被往上推

监听输入法

如何监听输入法的弹起收缩以及高度调节呢?核心代码在KeyboardUtil内部
        @Override
        public void onGlobalLayout() {
            final View userRootView = contentView.getChildAt(0);
            final View contentParentView = (View) contentView.getParent();

            // Step 1. calculate the current display frame's height.
            Rect r = new Rect();

            final int displayHeight;
            if (isTranslucentStatus) {
                contentParentView.getWindowVisibleDisplayFrame(r);
                displayHeight = (r.height()) + statusBarHeight;
            } else {
                //得到的区域不包括状态栏,底部导航栏,输入法键盘
                userRootView.getWindowVisibleDisplayFrame(r);
                displayHeight = r.height();
            }

            calculateKeyboardHeight(displayHeight);
            calculateKeyboardShowing(displayHeight);

            previousDisplayHeight = displayHeight;
        }
核心思想就是在onGlobalLayout被触发的时候,获取一个displayHeight的高度,然后根据高度来判断输入法是否弹起(calculateKeyboardShowing),输入法键盘高度是否调整(calculateKeyboardHeight)。
这里逻辑比较复杂,以后我会修改代码,让逻辑清楚一些,这里先简单过一下
第一步计算displayHeight的时候要根据是否透明来做不同处理,这里的contentParentView就是actionBarOverlayout,
            if (isTranslucentStatus) {
                contentParentView.getWindowVisibleDisplayFrame(r);
                displayHeight = (r.height()) + statusBarHeight;
            } else {
                //得到的区域不包括状态栏,底部导航栏,输入法键盘
                userRootView.getWindowVisibleDisplayFrame(r);
                displayHeight = r.height();
            }
第二部calculateKeyboardHeight的过程中也有类似的代码,根据activity的透明度,全屏,isFitsSystem信息来进行分别对待
         int keyboardHeight;
            if (KPSwitchConflictUtil.isHandleByPlaceholder(isFullScreen, isTranslucentStatus,
                    isFitSystemWindows)) {
                // the height of content parent = contentView.height + actionBar.height
                final View actionBarOverlayLayout = (View) contentView.getParent();

                keyboardHeight = actionBarOverlayLayout.getHeight() - displayHeight;

                Log.d(TAG, String.format("action bar over layout %d display height: %d",
                        ((View) contentView.getParent()).getHeight(), displayHeight));

            } else {
                keyboardHeight = Math.abs(displayHeight - previousDisplayHeight);
            }
第三部calculateKeyboardShowing也有类似的代码
    if (KPSwitchConflictUtil.isHandleByPlaceholder(isFullScreen, isTranslucentStatus,
                    isFitSystemWindows)) {
                if (!isTranslucentStatus &&
                        actionBarOverlayLayoutHeight - displayHeight == this.statusBarHeight) {
                    // handle the case of status bar layout, not keyboard active.
                    isKeyboardShowing = lastKeyboardShowing;
                } else {
                    isKeyboardShowing = actionBarOverlayLayoutHeight > displayHeight;
                }

            } else {

                final int phoneDisplayHeight = contentView.getResources().getDisplayMetrics().heightPixels;
                if (!isTranslucentStatus &&
                        phoneDisplayHeight == actionBarOverlayLayoutHeight) {
                    // no space to settle down the status bar, switch to fullscreen,
                    // only in the case of paused and opened the fullscreen page.
                    Log.w(TAG, String.format("skip the keyboard status calculate, the current" +
                                    " activity is paused. and phone-display-height %d," +
                                    " root-height+actionbar-height %d", phoneDisplayHeight,
                            actionBarOverlayLayoutHeight));
                    return;

                }

                if (maxOverlayLayoutHeight == 0) {
                    // non-used.
                    isKeyboardShowing = lastKeyboardShowing;
                } else if (displayHeight >= maxOverlayLayoutHeight) {
                    isKeyboardShowing = false;
                } else {
                    isKeyboardShowing = true;
                }

                maxOverlayLayoutHeight = Math.max(maxOverlayLayoutHeight, actionBarOverlayLayoutHeight);
            }




附录

/*
 * Copyright (C) 2015-2016 Jacksgong(blog.dreamtobe.cn)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.dreamtobe.kpswitch.handler;

import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import cn.dreamtobe.kpswitch.IPanelConflictLayout;
import cn.dreamtobe.kpswitch.util.StatusBarHeightUtil;
import cn.dreamtobe.kpswitch.util.ViewUtil;

/**
 * 此部分代码有所改动,与github不一致
 * Created by Jacksgong on 3/30/16.
 *
 * @see cn.dreamtobe.kpswitch.widget.KPSwitchRootFrameLayout
 * @see cn.dreamtobe.kpswitch.widget.KPSwitchRootLinearLayout
 * @see cn.dreamtobe.kpswitch.widget.KPSwitchRootRelativeLayout
 */
public class KPSwitchRootLayoutHandler {
    private final static String TAG = "KPSRootLayoutHandler";

    private int mOldHeight = -1;
    private final View mTargetRootView;

    private final int mStatusBarHeight;
    private final boolean mIsTranslucentStatus;
    private IPanelConflictLayout mPanelLayout;

    public KPSwitchRootLayoutHandler(final View rootView) {
        this.mTargetRootView = rootView;
        this.mStatusBarHeight = StatusBarHeightUtil.getStatusBarHeight(rootView.getContext());
        final Activity activity = (Activity) rootView.getContext();
        this.mIsTranslucentStatus = ViewUtil.isTranslucentStatus(activity);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public void handleBeforeMeasure(final int width, int height) {
        // 由当前布局被键盘挤压,获知,由于键盘的活动,导致布局将要发生变化。

        if (mIsTranslucentStatus && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            if (mTargetRootView.getFitsSystemWindows()) {
                // In this case, the height is always the same one, so, we have to calculate below.
                final Rect rect = new Rect();
                mTargetRootView.getWindowVisibleDisplayFrame(rect);
                height = rect.bottom - rect.top;
            }
        }

        Log.d(TAG, "onMeasure, width: " + width + " height: " + height);
        if (height < 0) {
            return;
        }

        if (mOldHeight < 0) {
            mOldHeight = height;
            return;
        }

        final int offset = mOldHeight - height;

        if (offset == 0) {
            Log.d(TAG, "" + offset + " == 0 break;");
            return;
        }

        if (Math.abs(offset) == mStatusBarHeight) {
            Log.w(TAG, String.format("offset just equal statusBar height %d", offset));
            // 极有可能是 相对本页面的二级页面的主题是全屏&是透明,但是本页面不是全屏,因此会有status bar的布局变化差异,进行调过
            // 极有可能是 该布局采用了透明的背景(windowIsTranslucent=true),而背后的布局`full screen`为false,
            // 因此有可能第一次绘制时没有attach上status bar,而第二次status bar attach上去,导致了这个变化。
            return;
        }

        mOldHeight = height;
//        final IPanelConflictLayout panel = getPanelLayout(mTargetRootView);
        if(mPanelLayout==null){
            getPanelLayout(mTargetRootView);
        }
        if (mPanelLayout == null) {
            Log.w(TAG, "can't find the valid panel conflict layout, give up!");
            return;
        }

        // 检测到真正的 由于键盘收起触发了本次的布局变化

        if (offset > 0) {
            //键盘弹起 (offset > 0,高度变小)
            mPanelLayout.handleHide();
        } else if (mPanelLayout.isKeyboardShowing()) {
            // 1. 总得来说,在监听到键盘已经显示的前提下,键盘收回才是有效有意义的。
            // 2. 修复在Android L下使用V7.Theme.AppCompat主题,进入Activity,默认弹起面板bug,
            // 第2点的bug出现原因:在Android L下使用V7.Theme.AppCompat主题,并且不使用系统的ActionBar/ToolBar,V7.Theme.AppCompat主题,还是会先默认绘制一帧默认ActionBar,然后再将他去掉(略无语)
            //键盘收回 (offset < 0,高度变大)
            if (mPanelLayout.isVisible()) {
                // the panel is showing/will showing
                mPanelLayout.handleShow();
            }
        }
    }



    private IPanelConflictLayout getPanelLayout(final View view) {
        if (mPanelLayout != null) {
            return mPanelLayout;
        }

        if (view instanceof IPanelConflictLayout) {
            mPanelLayout = (IPanelConflictLayout) view;
            return mPanelLayout;
        }

        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                IPanelConflictLayout v = getPanelLayout(((ViewGroup) view).getChildAt(i));
                if (v != null) {
                    mPanelLayout = v;
                    return mPanelLayout;
                }
            }
        }

        return null;
    }
}







  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
蛋白质是生物体中普遍存在的一类重要生物大分子,由天然氨基酸通过肽键连接而成。它具有复杂的分子结构和特定的生物功能,是表达生物遗传性状的一类主要物质。 蛋白质的结构可分为四级:一级结构是组成蛋白质多肽链的线性氨基酸序列;二级结构是依靠不同氨基酸之间的C=O和N-H基团间的氢键形成的稳定结构,主要为α螺旋和β折叠;三级结构是通过多个二级结构元素在三维空间的排列所形成的一个蛋白质分子的三维结构;四级结构用于描述由不同多肽链(亚基)间相互作用形成具有功能的蛋白质复合物分子。 蛋白质在生物体内具有多种功能,包括提供能量、维持电解质平衡、信息交流、构成人的身体以及免疫等。例如,蛋白质分解可以为人体提供能量,每克蛋白质能产生4千卡的热能;血液里的蛋白质能帮助维持体内的酸碱平衡和血液的渗透压;蛋白质是组成人体器官组织的重要物质,可以修复受损的器官功能,以及维持细胞的生长和更新;蛋白质也是构成多种生理活性的物质,如免疫球蛋白,具有维持机体正常免疫功能的作用。 蛋白质的合成是指生物按照从脱氧核糖核酸(DNA)转录得到的信使核糖核酸(mRNA)上的遗传信息合成蛋白质的过程。这个过程包括氨基酸的活化、多肽链合成的起始、肽链的延长、肽链的终止和释放以及蛋白质合成后的加工修饰等步骤。 蛋白质降解是指食物中的蛋白质经过蛋白质降解酶的作用降解为多肽和氨基酸然后被人体吸收的过程。这个过程在细胞的生理活动中发挥着极其重要的作用,例如将蛋白质降解后成为小分子的氨基酸,并被循环利用;处理错误折叠的蛋白质以及多余组分,使之降解,以防机体产生错误应答。 总的来说,蛋白质是生物体内不可或缺的一类重要物质,对于维持生物体的正常生理功能具有至关重要的作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值