尝试制作一个包含 40 列或字符的文本控制台

trying to make a text console with 40 columns or characters

我正在扩展 MultiAutoCompleteTextView 并将字体设置为此字体 Unicode font

这是我声明的 xml

<jacs.apps.jacs.CustomViews.Console
        android:id="@+id/auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:dropDownAnchor="@id/content_frame"
        android:dropDownHeight="100dp"
        android:layout_weight="1"
        android:gravity="bottom"
        android:inputType="textMultiLine|textNoSuggestions"
        android:fontFamily="@font/unifont"
        android:imeOptions="actionDone"
        android:scrollbars="vertical"
        android:scrollHorizontally="false"
        android:singleLine="false"
        android:typeface="monospace"

        />

这是我的 class

class Console : AppCompatMultiAutoCompleteTextView {
    private var mCharHeight = 0
    private var h: Int = 0
    private var mIsSearchEnabled = true
    protected val heightVisible: Int
        get() {
            val rect = Rect()
            getWindowVisibleDisplayFrame(rect)
            return rect.bottom - rect.top
        }

    constructor(context: Context) : super(context) {}

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}

    private fun refitText(text: String, textWidth: Int) {
        val mTestPaint = Paint()
        mTestPaint.set(this.paint)
        if (textWidth <= 0)
            return
        val targetWidth = textWidth - this.paddingLeft - this.paddingRight
        var hi = 100f
        var lo = 2f
        val threshold = 0.5f // How close we have to be

        mTestPaint.set(this.paint)

        while (hi - lo > threshold) {
            val size = (hi + lo) / 2
            mTestPaint.textSize = size
            if (mTestPaint.measureText(text) >= targetWidth)
                hi = size // too big
            else
                lo = size // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo)
        Log.d("baseline", "textsize: $textSize")

    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
        val height = measuredHeight
        refitText("mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", parentWidth)
        this.setMeasuredDimension(parentWidth, height)

    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        onDropDownChangeSize(w, h)
    }

    protected fun onDropDownChangeSize(w: Int, h: Int) {
        val rect = Rect()
        getWindowVisibleDisplayFrame(rect)
        //Logger.debug(TAG, "onDropdownChangeSize: " + rect);
        // 1/2 width of screen
        dropDownWidth = (w * 0.5f).toInt()
        // 0.5 height of screen
        //setDropDownHeight((int) (h * 1f));
        dropDownHeight = 300
        this.h = h
        Log.d("suggestions", "h : $h")
        //change position
        onPopupChangePosition()
    }

    fun setSearchEnabledTrue() {
        mIsSearchEnabled = true
        setSearchEnabled(true)

    }

    fun setSearchEnabled(isEnabled: Boolean) {
        mIsSearchEnabled = isEnabled
    }

    override fun performFiltering(text: CharSequence, keyCode: Int) {
        if (mIsSearchEnabled) {
            super.performFiltering(text, keyCode)
        }
    }

    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        val conn = super.onCreateInputConnection(outAttrs)
        outAttrs.imeOptions = outAttrs.imeOptions and EditorInfo.IME_FLAG_NO_ENTER_ACTION.inv()
        return conn
    }

    override fun showDropDown() {
        if (mIsSearchEnabled) {
            onPopupChangePosition()

            super.showDropDown()
        }

    }

    protected fun invalidateCharHeight() {
        mCharHeight = Math.ceil(paint.fontSpacing.toDouble()).toInt()
        mCharHeight = paint.measureText("M").toInt()
    }

    protected fun onPopupChangePosition() {
        try {
            val layout = layout
            invalidateCharHeight()
            if (layout != null) {

                val pos = selectionStart
                val line = layout.getLineForOffset(pos)
                val baseline = layout.getLineBaseline(line)
                val ascent = layout.getLineAscent(line)

                val bounds = Rect()
                val textPaint = paint
                val sample = "A"
                textPaint.getTextBounds(sample, 0, sample.length, bounds)
                val width = bounds.width() / sample.length


                val x = layout.getPrimaryHorizontal(pos)
                val y = (baseline + ascent).toFloat()

                val offsetHorizontal = x.toInt() + getWidth()
                dropDownHorizontalOffset = offsetHorizontal

                val heightVisible = heightVisible
                val offsetVertical = (y + mCharHeight - scrollY).toInt()

                val tmp = -h + offsetVertical + dropDownHeight + mCharHeight

                //if (tmp < heightVisible) {
                //tmp = -h + ((offsetVertical*2 / (mCharHeight)) * (mCharHeight / 2))+(mCharHeight/2);
                dropDownVerticalOffset = tmp
                Log.d("suggestions", "tmp : $tmp")
                //((Activity)(mContext)).setTitle("ov :"+offsetVertical +" ch "+mCharHeight+" tmp"+tmp +"h "+h+"p:"+pos);
                //                } else {
                //                    tmp = offsetVertical - getDropDownHeight() - mCharHeight;
                //                    setDropDownVerticalOffset(tmp);
                //                    ((Activity)(mContext)).setTitle(" 2 tmp :"+tmp);
                //                }


                //                int pos = getSelectionStart();
                //                int line = layout.getLineForOffset(pos);
                //                int baseline = layout.getLineBaseline(line);
                //                int ascent = layout.getLineAscent(line);
                //
                //                float x = layout.getPrimaryHorizontal(pos);
                //                float y = baseline + ascent;
                //
                //                int offsetHorizontal = (int) x + mGutterWidth;
                //                setDropDownHorizontalOffset(offsetHorizontal);
                //
                //                //    int heightVisible = getHeightVisible();
                //                int offsetVertical = (int) ((y + mCharHeight) - getScrollY());
                //
                //                int tmp = offsetVertical + getDropDownHeight() + mCharHeight;
                ////                if (tmp < heightVisible) {
                //                tmp = -(offsetVertical + mCharHeight) + ((offsetVertical / mCharHeight) * (mCharHeight / 2));
                //                setDropDownVerticalOffset(tmp);
                ////                } else {
                ////                    tmp = offsetVertical - getDropDownHeight() - mCharHeight;
                ////                    setDropDownVerticalOffset(tmp);
                ////                }

            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    companion object {
        private val MINIMAL_HEIGHT = 100
    }

}

我试图让每台设备的视图宽度为 40 个字符,它可以在一堆不同的设备上工作,但是当我 运行 它在分辨率为 720X1560 的模拟器上时,它让每行 41 个字符所需的 40 个。我正在尝试找到一种方法来调整字体大小,以便每行有 40 个字符。

编辑 放置 android:paddingRight="35dp" 似乎可以解决问题,尽管我想确定我也不能使用换行符,因为我已经将它们用于一种特殊的解析

感谢您的宝贵时间

我修改了refitText方法如下:

private fun refitText(columns: Int, textWidth: Int) {
    val mTestPaint = Paint()
    mTestPaint.set(this.paint)
    if (textWidth <= 0)
        return
    val targetWidth = textWidth - this.paddingLeft - this.paddingRight
    val maxTextSize = 1000000f
    mTestPaint.textSize = maxTextSize
    val maxCharWidth = mTestPaint.measureText("m")
    var size = targetWidth * maxTextSize / (maxCharWidth * columns)
    do {
        mTestPaint.textSize = size
        val realCharWidth = mTestPaint.measureText("m")
        val requiredPadding = targetWidth - realCharWidth * columns;
        if (requiredPadding >= 0) {
            this.setPadding(this.paddingLeft + requiredPadding.toInt() / 2, this.paddingTop, this.paddingRight + requiredPadding.toInt() / 2, this.paddingBottom)
            break
        }
        size *= 1 - (Math.abs(requiredPadding) / targetWidth)
    } while (requiredPadding < 0)
    this.setTextSize(TypedValue.COMPLEX_UNIT_PX, size)

    Log.d("baseline", "textsize: $textSize")

}

字符宽度必须是像素的正整数,因为不可能在显示器上打开一半像素并关闭另一半像素。 假设您计算出的字体大小可以在 720 像素宽度的显示器中呈现 39 个字符。这意味着每个字符的宽度为 18 像素(720/39=18)。另一方面,要呈现 40 个字符,您必须减小字体大小。在这种情况下,减小字体大小后,最大字符宽度可以达到 17 像素。现在前39个字符将显示在663像素(39*17=663)上,还有57个空像素(720-663=57)足以再渲染3个字符(57/17=3 ) 在那一行。在这种情况下,您需要另一个限制。所以你应该添加一些动态计算的填充来控制一行中的字符数。