根据光标所在位置和屏幕可用高度,通过锚定在编辑文本下方或上方显示 RecyclerView/PopupWIndow

Show RecyclerView/PopupWIndow by anchoring below or above edittext depending on where the cursor is and depending on screen available height

我正在尝试显示 @ 用户提及,例如 facebook 或 twitter。我能够实现这个功能。但是我有一个 ui 问题。

如果 edittext 在屏幕底部,我需要在顶部的 recyclerview 中显示建议。(在 edittext 光标上方)

如果 edittext 在屏幕顶部,我需要在底部的 recycelrview 中显示建议(在 edittext 光标下方)

我尝试了什么?

我使用了限制在 edittext 下方的 recyclerview

问题

软键盘打开时覆盖recyclerview。当 edittext 光标位于底部时会发生这种情况。

我如何使用现有的 recyclerview 本身解决这个问题?

当 edittext 光标位于顶部时,请参阅下面正确显示的建议。

代码

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:textStyle="bold"
            android:textSize="16sp"
            android:textColor="@color/black"
            android:fontFamily="@font/source_sans_pro"
            android:id="@+id/titleHeader"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:text="@string/create_feed_post"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <View
            android:id="@+id/line"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginTop="16dp"
            android:background="#c8c8c8"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/titleHeader" />


        <com.linkedin.android.spyglass.ui.MentionsEditText
            android:padding="8dp"
            android:id="@+id/mentionsEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxHeight="300dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:background="@null"
            android:gravity="start|top"
            android:hint="Share team wins or recognize colleague for a job well done"
            android:inputType="textMultiLine"
            android:minHeight="50dp"
            android:paddingStart="15dp"
            android:paddingEnd="15dp"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/line">

        </com.linkedin.android.spyglass.ui.MentionsEditText>

        <ImageView
            android:id="@+id/preview"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:visibility="visible"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/mentionsEditText" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="0dp"
            android:layout_height="250dp"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:elevation="5dp"
            android:orientation="vertical"
            android:overScrollMode="never"
            android:scrollbarStyle="outsideOverlay"
            android:scrollbars="none"
            android:visibility="gone"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/mentionsEditText" />

        <View
            android:id="@+id/line2"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginTop="8dp"
            android:background="#c8c8c8"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/preview" />


        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/optionsContainer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/line2">

            <ImageView
                android:id="@+id/visibility"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:src="@drawable/toggle_comment"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/attach"
                app:layout_constraintTop_toTopOf="parent" />

            <ImageView
                android:id="@+id/attach"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_upload_image" />

            <Button
                android:id="@+id/done"
                android:layout_width="0dp"
                android:layout_height="60dp"
                android:layout_marginStart="32dp"
                android:text="@string/post"
                android:textAllCaps="false"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="@id/visibility"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <ImageView
            android:id="@id/cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            app:layout_constraintBottom_toBottomOf="@+id/titleHeader"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="@+id/titleHeader"
            app:srcCompat="@drawable/ic_close"
            app:tint="@color/black" />

        <FrameLayout
            android:visibility="gone"
            android:elevation="5dp"
            android:id="@+id/container"
            app:layout_constraintBottom_toTopOf="@+id/preview"
            app:layout_constraintEnd_toEndOf="@+id/preview"
            app:layout_constraintTop_toTopOf="@+id/preview"
            android:layout_width="20dp"
            android:layout_height="20dp">
            <com.mikhaellopez.circleview.CircleView
                app:cv_border_width="1dp"
                app:cv_border_color="#EAEAEA"
                app:cv_border="true"
                app:cv_color="@color/white"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

            <ImageView
                android:layout_gravity="center"
                android:id="@+id/removePreview"
                android:layout_width="15dp"
                android:layout_height="15dp"
                app:srcCompat="@drawable/ic_close"
                app:tint="#999999" />

        </FrameLayout>
        
    </androidx.constraintlayout.widget.ConstraintLayout>
    </ScrollView>
</layout>

更新:我尝试使用弹出窗口 window。根据视图所在的位置,我可以在视图上方或下方显示。但是键盘打开还有一个问题。当编辑文本位于底部时,弹出窗口会出现在键盘上。 edittext 向上移动,但弹出 window 留在原处

更新 2:

    if(anchor instanceof EditText) {
        EditText editText = (EditText) anchor;
        int pos = editText.getSelectionStart();
        Layout layout = editText.getLayout();
        int line = layout.getLineForOffset(pos);
        int baseline = layout.getLineBaseline(line);
        int ascent = layout.getLineAscent(line);
        float cursorx = layout.getPrimaryHorizontal(pos);
        cursory = baseline + ascent- editText.getScrollY();
        
    }



    final View contentView = view;

    final Rect windowRect = new Rect();
    contentView.getWindowVisibleDisplayFrame(windowRect);
    final int windowW = windowRect.width();
    final int windowH = windowRect.height();
    contentView.measure(
            makeDropDownMeasureSpec(getWidth(), windowW),
            makeDropDownMeasureSpec(getHeight(), windowH)
    );
    final int measuredW = contentView.getMeasuredWidth();
    final int measuredH = contentView.getMeasuredHeight();
    final int[] anchorLocation = new int[2];
    anchor.getLocationInWindow(anchorLocation);
    final int anchorBottom = anchorLocation[1] + anchor.getHeight();
    

      if (y + anchorBottom < 0) {
            y = -anchorBottom;
        } else  {
          y = (int) (y + cursory );

       }

然后

popupWindow.showAsDropDown(anchor, 0, y);

清单中的标志是

android:windowSoftInputMode="adjustResize"

使用弹出 window 在位置显示将解决此问题

private fun showPopupWindow(anchor: View) {
    var popUpHeight = 0
    PopupWindow(anchor.context).apply {
        isOutsideTouchable = true
        val inflater = from(anchor.context)
        contentView = inflater.inflate(R.layout.popup_layout, null).apply {
            measure(
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            )
            measuredWidth
            measuredHeight
            popUpHeight = measuredHeight
        }
        height = ViewGroup.LayoutParams.WRAP_CONTENT
        width = ViewGroup.LayoutParams.MATCH_PARENT
    }.also { popupWindow ->
        val location = IntArray(2).apply {
            anchor.getLocationInWindow(this)
        }
        val size = Size(
            popupWindow.contentView.measuredWidth,
            popupWindow.contentView.measuredHeight
        )
        val editText = anchor as EditText
        val pos = editText.selectionStart
        val layout: Layout = editText.layout
        val line: Int = layout.getLineForOffset(pos)
        editText.getLocationOnScreen(location)
        val point = Point()
        point.x = layout.getPrimaryHorizontal(pos).toInt()
        point.y = layout.getLineBottom(line)// location[1] //-  (baseline + ascent )
        val y = point.y
        val top = layout.getLineTop(line)
        val editTextheight = editText.measuredHeight
        val yOffset = if (y > (editTextheight / 2)) {
             top - (popUpHeight + 50)
         } else {
              point.y + 50
          }
          popupWindow.showAsDropDown(
              anchor, 0,
              yOffset
         )
    }
}

根据光标所在的位置以及编辑文本的高度,您可以计算 yoffset 并在指定位置显示弹出窗口。

如果 edittext 滚动,您可以使用 editText.scrolly 和 re-calculate 您的偏移量。