Как захватить событие "показать / скрыть виртуальную клавиатуру" в Android?
Я хотел бы изменить макет в зависимости от того, отображается виртуальная клавиатура или нет. Я искал API и различные блоги, но не могу найти ничего полезного.
Является ли это возможным?
Спасибо!
22 ответа
Вы должны справиться с изменениями конфигурации самостоятельно.
http://developer.android.com/guide/topics/resources/runtime-changes.html
Образец:
// from the link above
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks whether a hardware keyboard is available
if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
} else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
}
}
Затем просто измените видимость некоторых видов, обновите поле и измените файл макета.
Заметка
Это решение не будет работать для мягких клавиатур и onConfigurationChanged
не будет вызываться для мягких клавиатур.
Я сделал это так:
добавлять OnKeyboardVisibilityListener
интерфейс.
public interface OnKeyboardVisibilityListener {
void onVisibilityChanged(boolean visible);
}
HomeActivity.java:
public class HomeActivity extends Activity implements OnKeyboardVisibilityListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sign_up);
// Other stuff...
setKeyboardVisibilityListener(this);
}
private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
final View parentView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
private boolean alreadyOpen;
private final int defaultKeyboardHeightDP = 100;
private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
private final Rect rect = new Rect();
@Override
public void onGlobalLayout() {
int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
parentView.getWindowVisibleDisplayFrame(rect);
int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
boolean isShown = heightDiff >= estimatedKeyboardHeight;
if (isShown == alreadyOpen) {
Log.i("Keyboard state", "Ignoring global layout change...");
return;
}
alreadyOpen = isShown;
onKeyboardVisibilityListener.onVisibilityChanged(isShown);
}
});
}
@Override
public void onVisibilityChanged(boolean visible) {
Toast.makeText(HomeActivity.this, visible ? "Keyboard is active" : "Keyboard is Inactive", Toast.LENGTH_SHORT).show();
}
}
Надеюсь, это поможет вам.
Это может быть не самым эффективным решением. Но это работало для меня каждый раз... Я вызываю эту функцию везде, где мне нужно слушать softKeyboard.
boolean isOpened = false;
public void setListenerToRootView() {
final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();
if (isOpened == false) {
//Do two things, make the view top visible and the editText smaller
}
isOpened = true;
} else if (isOpened == true) {
Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
isOpened = false;
}
}
});
}
Примечание: этот подход вызовет проблемы, если пользователь использует плавающую клавиатуру.
Если вы хотите обработать отображение / скрытие окна виртуальной клавиатуры IMM из вашей Activity, вам нужно создать подкласс вашего макета и переопределить метод onMesure (чтобы вы могли определить измеренную ширину и измеренную высоту вашего макета). После этого установите подклассный макет в качестве основного вида для вашей деятельности с помощью setContentView(). Теперь вы сможете обрабатывать события отображения / скрытия окна IMM. Если это звучит сложно, это не так. Вот код:
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<EditText
android:id="@+id/SearchText"
android:text=""
android:inputType="text"
android:layout_width="fill_parent"
android:layout_height="34dip"
android:singleLine="True"
/>
<Button
android:id="@+id/Search"
android:layout_width="60dip"
android:layout_height="34dip"
android:gravity = "center"
/>
</LinearLayout>
Теперь внутри вашей Activity объявите подкласс для вашего макета (main.xml)
public class MainSearchLayout extends LinearLayout {
public MainSearchLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.main, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("Search Layout", "Handling Keyboard Window shown");
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
final int actualHeight = getHeight();
if (actualHeight > proposedheight){
// Keyboard is shown
} else {
// Keyboard is hidden
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Из кода видно, что мы раздуваем компоновку для нашей Деятельности в конструкторе подкласса
inflater.inflate(R.layout.main, this);
А теперь просто настройте представление содержимого подклассного макета для нашей Деятельности.
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainSearchLayout searchLayout = new MainSearchLayout(this, null);
setContentView(searchLayout);
}
// rest of the Activity code and subclassed layout...
}
Как и ответ @amalBit, зарегистрируйте прослушиватель для глобальной компоновки и вычислите разницу видимого дна dectorView и его предполагаемого дна. Если разница больше некоторого значения (предполагаемая высота IME), мы думаем, что IME работает:
final EditText edit = (EditText) findViewById(R.id.edittext);
edit.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (keyboardShown(edit.getRootView())) {
Log.d("keyboard", "keyboard UP");
} else {
Log.d("keyboard", "keyboard Down");
}
}
});
private boolean keyboardShown(View rootView) {
final int softKeyboardHeight = 100;
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
int heightDiff = rootView.getBottom() - r.bottom;
return heightDiff > softKeyboardHeight * dm.density;
}
порог высоты 100 - это предполагаемая минимальная высота IME.
Это работает как для AdjustPan, так и AdjustResize.
Основываясь на коде от Nebojsa Tomcic, я разработал следующий RelativeLayout-Subclass:
import java.util.ArrayList;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
public class KeyboardDetectorRelativeLayout extends RelativeLayout {
public interface IKeyboardChanged {
void onKeyboardShown();
void onKeyboardHidden();
}
private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();
public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public KeyboardDetectorRelativeLayout(Context context) {
super(context);
}
public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
keyboardListener.add(listener);
}
public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
keyboardListener.remove(listener);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
final int actualHeight = getHeight();
if (actualHeight > proposedheight) {
notifyKeyboardShown();
} else if (actualHeight < proposedheight) {
notifyKeyboardHidden();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void notifyKeyboardHidden() {
for (IKeyboardChanged listener : keyboardListener) {
listener.onKeyboardHidden();
}
}
private void notifyKeyboardShown() {
for (IKeyboardChanged listener : keyboardListener) {
listener.onKeyboardShown();
}
}
}
Это работает довольно хорошо... Отметьте, что это решение будет просто работать, когда режим мягкого ввода вашей активности установлен в "WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE"
Решение для версии до Android 11:
Поскольку выпущен androidx.core 1.5.0, это то, что я делаю, чтобы прослушивать событие отображения / скрытия клавиатуры на устройствах до Android 11.
gradle:
implementation "androidx.core:core-ktx:1.5.0"
фрагмент:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val view = activity?.window?.decorView ?: return
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val showingKeyboard = insets.isVisible(WindowInsetsCompat.Type.ime())
if(showingKeyboard){
//do something
}
insets
}
}
убедитесь, что вы удалили слушателя при уничтожении представления, чтобы избежать утечки памяти.
Обратите внимание, что согласно документу,
* When running on devices with API Level 29 and before, the returned value is an
* approximation based on the information available. This is especially true for the {@link
* Type#ime IME} type, which currently only works when running on devices with SDK level 23
* and above.
*
insert.isVisible (ime) должен работать только на устройствах с уровнем SDK выше 23
Решение Небойши почти сработало для меня. Когда я щелкнул внутри многострочного EditText, он знал, что клавиатура была отображена, но когда я начал печатать внутри EditText, actualHeight и предлагаемый Height были одинаковыми, поэтому он не знал, что клавиатура все еще отображается. Я сделал небольшую модификацию, чтобы сохранить максимальную высоту, и она отлично работает. Вот пересмотренный подкласс:
public class CheckinLayout extends RelativeLayout {
private int largestHeight;
public CheckinLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.checkin, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
largestHeight = Math.max(largestHeight, getHeight());
if (largestHeight > proposedheight)
// Keyboard is shown
else
// Keyboard is hidden
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Не уверен, если кто-нибудь опубликовать это. Нашел это решение простым в использовании!, Класс SoftKeyboard находится на gist.github.com. Но в то время как клавиатура возвращает / скрывает обратный вызов события, нам нужен обработчик для правильного выполнения действий в пользовательском интерфейсе:
/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);
/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{
@Override
public void onSoftKeyboardHide()
{
// Code here
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Code here will run in UI thread
...
}
});
}
@Override
public void onSoftKeyboardShow()
{
// Code here
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Code here will run in UI thread
...
}
});
}
});
Я решаю это путем переопределения onKeyPreIme(int keyCode, KeyEvent event) в моем пользовательском EditText.
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
//keyboard will be hidden
}
}
У меня есть что-то вроде этого. Хотя, кажется, нет способа обнаружить, когда программная клавиатура показана или скрыта, вы можете фактически определить, когда она собирается показать или скрыться, установив OnFocusChangeListener
на EditText
что вы слушаете.
EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View view, boolean hasFocus)
{
//hasFocus tells us whether soft keyboard is about to show
}
});
ПРИМЕЧАНИЕ. Одна вещь, о которой следует помнить с этим взломом, заключается в том, что этот обратный вызов запускается сразу же, когда EditText
получает или теряет фокус. Это на самом деле срабатывает прямо перед тем, как программная клавиатура показывает или скрывается. Лучший способ сделать что-либо после того, как клавиатура показывает или скрывается, это использовать Handler
и задержать что-то ~ 400 мс, вот так:
EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View view, boolean hasFocus)
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
//do work here
}
}, 400);
}
});
Несмотря на то, что говорит решение, получившее наибольшее количество голосов на этой странице, существует версия ViewCompat setWindowInsetsAnimationCallback, которая работает вплоть до Android 21.
Так что теперь подход в этом решении работает вплоть до 21.
Сандер, я думаю, ты пытаешься показать вид, заблокированный программной клавиатурой. Попробуйте это http://android-developers.blogspot.com/2009/04/updating-applications-for-on-screen.html.
Приведенный выше ответ @ Filipkowicz отлично работает в Android API <30. Начиная с Android API 30 мы должны использовать
setWindowInsetsAnimationCallback
. Итак, ниже ответ объединяет оба метода для работы API 21-30.
private fun isKeyboardVisible(insets: WindowInsets): Boolean {
val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets)
val systemWindow = insetsCompat.systemWindowInsets
val rootStable = insetsCompat.stableInsets
if (systemWindow.bottom > rootStable.bottom) {
// This handles the adjustResize case on < API 30, since
// systemWindow.bottom is probably going to be the IME
return true
}
return false
}
@JvmStatic
@BindingAdapter("goneWhenKeyboardVisible")
fun View.goneWhenKeyboardVisible() {
if (isRPlus()) {
setWindowInsetsAnimationCallback(object :
WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
override fun onProgress(
insets: WindowInsets,
runningAnimations: MutableList<WindowInsetsAnimation>
): WindowInsets {
return insets
}
override fun onStart(
animation: WindowInsetsAnimation,
bounds: WindowInsetsAnimation.Bounds
): WindowInsetsAnimation.Bounds {
if (isVisible)
isVisible = !rootWindowInsets.isVisible(WindowInsets.Type.ime())
return super.onStart(animation, bounds)
}
override fun onEnd(animation: WindowInsetsAnimation) {
super.onEnd(animation)
if (!isVisible)
isVisible = !rootWindowInsets.isVisible(WindowInsets.Type.ime())
}
})
} else {
setOnApplyWindowInsetsListener { _, insets ->
isVisible = !isKeyboardVisible(insets)
insets
}
}
}
Вы также можете проверить наличие дочернего нижнего отступа первого DecorView. Он будет установлен в ненулевое значение при отображении клавиатуры.
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
View view = getRootView();
if (view != null && (view = ((ViewGroup) view).getChildAt(0)) != null) {
setKeyboardVisible(view.getPaddingBottom() > 0);
}
super.onLayout(changed, left, top, right, bottom);
}
Я решил проблему с однострочным кодированием текста.
package com.helpingdoc;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
public class MainSearchLayout extends LinearLayout {
int hieght = 0;
public MainSearchLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.main, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("Search Layout", "Handling Keyboard Window shown");
if(getHeight()>hieght){
hieght = getHeight();
}
final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
final int actualHeight = getHeight();
System.out.println("....hieght = "+ hieght);
System.out.println("....actualhieght = "+ actualHeight);
System.out.println("....proposedheight = "+ proposedheight);
if (actualHeight > proposedheight){
// Keyboard is shown
} else if(actualHeight<proposedheight){
// Keyboard is hidden
}
if(proposedheight == hieght){
// Keyboard is hidden
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
то, что я сделал, - это создание простой привязки, чтобы скрыть представление, когда отображается клавиатура. Решение основано на текущей реализации AndroidX для
WindowInsetsCompat
который все еще находится в стадии бета-тестирования (androidx core 1.5) - источник
private fun isKeyboardVisible(insets: WindowInsets): Boolean {
val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets)
val systemWindow = insetsCompat.systemWindowInsets
val rootStable = insetsCompat.stableInsets
if (systemWindow.bottom > rootStable.bottom) {
// This handles the adjustResize case on < API 30, since
// systemWindow.bottom is probably going to be the IME
return true
}
return false
}
@BindingAdapter("goneWhenKeyboardVisible")
fun View.goneWhenKeyboardVisible(enabled: Boolean) {
if (enabled) {
setOnApplyWindowInsetsListener { view, insets ->
visibility = if (isKeyboardVisible(insets)) GONE else VISIBLE
insets
}
} else {
setOnApplyWindowInsetsListener(null)
visibility = VISIBLE
}
}
Применение:
<FrameLayout
android:id="@+id/bottom_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:goneWhenKeyboardVisible="@{true}"
/>
Скрыть | Показать события для клавиатуры можно прослушать простым взломом в OnGlobalLayoutListener:
final View activityRootView = findViewById(R.id.top_root);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
if (heightDiff > 100) {
// keyboard is up
} else {
// keyboard is down
}
}
});
Здесь ActivityRootView - это корневой вид вашей активности.
Используя viewTreeObserver, чтобы легко получить событие клавиатуры.
layout_parent.viewTreeObserver.addOnGlobalLayoutListener {
val r = Rect()
layout_parent.getWindowVisibleDisplayFrame(r)
if (layout_parent.rootView.height - (r.bottom - r.top) > 100) { // if more than 100 pixels, its probably a keyboard...
Log.e("TAG:", "keyboard open")
} else {
Log.e("TAG:", "keyboard close")
}
}
** layout_parent - это ваше мнение какedit_text.parent
Для пользователей Kotlin это вдохновлено этим ответом в комментариях, вы можете создать расширение:
import android.graphics.Rect
import android.view.View
import android.widget.TextView
const val SOFT_KEYBOARD_HEIGHT = 100
fun TextView.addOnKeyboardVisibilityListener(
onKeyboardShown: () -> Unit,
onKeyboardHidden: () -> Unit,
) {
viewTreeObserver.addOnGlobalLayoutListener {
if(rootView.isKeyboardShown()) {
onKeyboardShown()
} else {
onKeyboardHidden()
}
}
}
fun View.isKeyboardShown(): Boolean =
Rect().let { rect ->
rootView.getWindowVisibleDisplayFrame(rect)
rect
}.let {
rootView.bottom - it.bottom
}.let { heightDiff ->
heightDiff > SOFT_KEYBOARD_HEIGHT * rootView.resources.displayMetrics.density
}
и вы можете использовать его как:
editText.addOnKeyboardVisibilityListener(
onKeyboardShown = {
// TODO
},
onKeyboardHidden = {
// TODO
}
)
Решение для Android SDK 23–31 с
implementation "androidx.core:core-ktx:1.6.0"
:
window.decorView.viewTreeObserver.addOnGlobalLayoutListener {
val showingKeyboard = WindowInsetsCompat.toWindowInsetsCompat(
window.decorView.rootWindowInsets
).isVisible(WindowInsetsCompat.Type.ime())
// addOnGlobalLayoutListener can be called multiple times
// so it's better to check if status of keyboard was changed before doing something
if (binding.showingKeyboard != showingKeyboard) {
binding.showingKeyboard = showingKeyboard
}
}
Я тоже пытался
ViewCompat.setWindowInsetsAnimationCallback(window.decorView, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
override fun onEnd(animation: WindowInsetsAnimationCompat) {
super.onEnd(animation)
val showingKeyboard = WindowInsetsCompat.toWindowInsetsCompat(window.decorView.rootWindowInsets)
.isVisible(WindowInsetsCompat.Type.ime())
if (binding.showingKeyboard != showingKeyboard) {
binding.showingKeyboard = showingKeyboard
}
}
override fun onProgress(
p0: WindowInsetsCompat,
p1: MutableList<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
return WindowInsetsCompat.toWindowInsetsCompat(window.decorView.rootWindowInsets)
}
})
Но здесь мы должны преодолеть
onProgress()
метод и возврат
WindowInsetsCompat
, также пройти
DISPATCH_MODE_STOP
или
DISPATCH_MODE_CONTINUE_ON_SUBTREE
но я не уверен, какая из них правильная константа в этом случае. Поэтому я предпочитаю использовать
View.viewTreeObserver.addOnGlobalLayoutListener()
вместо этого
также
WindowInsetsAnimationCompat.Callback.onEnd()
вызывается с некоторой задержкой
Ответ Небойши Томчича мне не помог. я имею RelativeLayout
с TextView
а также AutoCompleteTextView
внутри него. Мне нужно прокрутить TextView
вниз, когда клавиатура показана и когда она скрыта. Для достижения этой цели я преодолел onLayout
метод, и он прекрасно работает для меня.
public class ExtendedLayout extends RelativeLayout
{
public ExtendedLayout(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.main, this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
if (changed)
{
int scrollEnd = (textView.getLineCount() - textView.getHeight() /
textView.getLineHeight()) * textView.getLineHeight();
textView.scrollTo(0, scrollEnd);
}
}
}