我试图在下面做类似于Instagram的事情 -
现在我又遇到了一个问题 -
当我打字时,文本不会自动进入下一行,我必须按返回,就像通常editText工作在固定宽度。 (简而言之multiline
与ReplacementSpan
)
以下是我所做的示例代码 -
public class EditextActivity extends AppCompatActivity {
EditText edittext;
RoundedBackgroundSpan roundedBackgroundSpan;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.editext_screen);
edittext=(EditText)findViewById(R.id.edittext);
// edittext.setText("Hello My name is Karandeep Atwal.\n\n Hii this is test");
roundedBackgroundSpan= new RoundedBackgroundSpan(Color.RED,Color.WHITE);
edittext.getText().setSpan(roundedBackgroundSpan, 0, edittext.getText().length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
public class RoundedBackgroundSpan extends ReplacementSpan implements LineHeightSpan {
private static final int CORNER_RADIUS = 15;
private static final int PADDING_X = 10;
private int mBackgroundColor;
private int mTextColor;
/**
* @param backgroundColor background color
* @param textColor text color
*/
public RoundedBackgroundSpan(int backgroundColor, int textColor) {
mBackgroundColor = backgroundColor;
mTextColor = textColor;
}
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
return (int) (PADDING_X + paint.measureText(text,start, end) + PADDING_X);
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
float width = paint.measureText(text,start, end);
RectF rect = new RectF(x, top, x + width + 2 * PADDING_X, bottom);
paint.setColor(mBackgroundColor);
canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint);
paint.setColor(mTextColor);
canvas.drawText(text, start, end, x + PADDING_X, y, paint);
}
@Override
public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fontMetricsInt) {
}
}
}
下面是我的xml -
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_gravity="center"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:padding="5dp"
android:background="@drawable/border"
android:id="@+id/edittext"
android:layout_centerInParent="true"
android:textColor="@android:color/black"
android:gravity="center"
android:hint="hi"
android:singleLine="false"
android:inputType="textMultiLine"
android:textSize="30sp"
android:maxWidth="100dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
以下是我使用setSpan
-
这是固定宽度的正常行为,我想要 -
答案 0 :(得分:3)
var express = require('express');
var app = express();
var exec = require('ssh-exec');
var arg1 = "abc";
var arg2 = "def";
var arg3 = "ghd";
exec(`"./test.sh" ${arg1} ${arg2} ${arg3}` {
user: 'ubuntu',
host: 'a.b.c.d'
}, function(err, stdout) {
if (err) {
throw err;
}
console.log(stdout);
console.log('success');
});
MainActivity.java
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_purple"
tools:context="com.tttzof.demotext.MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Enter text"
android:textSize="30sp"
android:gravity="center"
android:textColor="@android:color/black"
android:background="@android:color/transparent"
android:layout_gravity="center"/>
</FrameLayout>
BackgroundColorSpan.java
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.Spannable;
import android.text.TextWatcher;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText editText = (EditText) findViewById(R.id.editText);
int padding = dp(8);
int radius = dp(5);
final Object span = new BackgroundColorSpan(
Color.WHITE,
(float)padding,
(float) radius
);
editText.setShadowLayer(padding, 0f, 0f, 0);
editText.setPadding(padding, padding, padding, padding);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable s) {
s.setSpan(span, 0, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
});
}
private int dp(int value) {
return (int) (getResources().getDisplayMetrics().density * value + 0.5f);
}
}
答案 1 :(得分:3)
通过@ tttzof351增强BackgroundColorSpan
以支持对齐:
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.text.style.LineBackgroundSpan
import kotlin.math.abs
import kotlin.math.sign
class BackgroundColorSpan(backgroundColor: Int,
private val padding: Int,
private val radius: Int) : LineBackgroundSpan {
private val rect = RectF()
private val paint = Paint()
private val paintStroke = Paint()
private val path = Path()
private var prevWidth = -1f
private var prevLeft = -1f
private var prevRight = -1f
private var prevBottom = -1f
private var prevTop = -1f
private val ALIGN_CENTER = 0
private val ALIGN_START = 1
private val ALIGN_END = 2
init {
paint.color = backgroundColor
paintStroke.color = backgroundColor
}
private var align = ALIGN_CENTER
fun setAlignment(alignment: Int) {
align = alignment
}
override fun drawBackground(
c: Canvas,
p: Paint,
left: Int,
right: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
lnum: Int) {
val width = p.measureText(text, start, end) + 2f * padding
val shiftLeft: Float
val shiftRight: Float
when (align) {
ALIGN_START -> {
shiftLeft = 0f - padding
shiftRight = width + shiftLeft
}
ALIGN_END -> {
shiftLeft = right - width + padding
shiftRight = (right + padding).toFloat()
}
else -> {
shiftLeft = (right - width) / 2
shiftRight = right - shiftLeft
}
}
rect.set(shiftLeft, top.toFloat(), shiftRight, bottom.toFloat())
if (lnum == 0) {
c.drawRoundRect(rect, radius.toFloat(), radius.toFloat(), paint)
} else {
path.reset()
val difference = width - prevWidth
val diff = -sign(difference) * (2f * radius).coerceAtMost(abs(difference / 2f)) / 2f
path.moveTo(
prevLeft, prevBottom - radius
)
if (align != ALIGN_START) {
path.cubicTo(//1
prevLeft, prevBottom - radius,
prevLeft, rect.top,
prevLeft + diff, rect.top
)
} else {
path.lineTo(prevLeft, prevBottom + radius)
}
path.lineTo(
rect.left - diff, rect.top
)
path.cubicTo(//2
rect.left - diff, rect.top,
rect.left, rect.top,
rect.left, rect.top + radius
)
path.lineTo(
rect.left, rect.bottom - radius
)
path.cubicTo(//3
rect.left, rect.bottom - radius,
rect.left, rect.bottom,
rect.left + radius, rect.bottom
)
path.lineTo(
rect.right - radius, rect.bottom
)
path.cubicTo(//4
rect.right - radius, rect.bottom,
rect.right, rect.bottom,
rect.right, rect.bottom - radius
)
path.lineTo(
rect.right, rect.top + radius
)
if (align != ALIGN_END) {
path.cubicTo(//5
rect.right, rect.top + radius,
rect.right, rect.top,
rect.right + diff, rect.top
)
path.lineTo(
prevRight - diff, rect.top
)
path.cubicTo(//6
prevRight - diff, rect.top,
prevRight, rect.top,
prevRight, prevBottom - radius
)
} else {
path.lineTo(prevRight, prevBottom - radius)
}
path.cubicTo(//7
prevRight, prevBottom - radius,
prevRight, prevBottom,
prevRight - radius, prevBottom
)
path.lineTo(
prevLeft + radius, prevBottom
)
path.cubicTo(//8
prevLeft + radius, prevBottom,
prevLeft, prevBottom,
prevLeft, rect.top - radius
)
c.drawPath(path, paintStroke)
}
prevWidth = width
prevLeft = rect.left
prevRight = rect.right
prevBottom = rect.bottom
prevTop = rect.top
}
}
结果:
答案 2 :(得分:1)
我实现了新的RoundedBackgroundSpan.kt
类扩展LineBackgroundSpan
,因为它可以逐行绘制装饰层。
class RoundedBackgroundSpan(
backgroundColor: Int,
private val padding: Float,
private val radius: Float
) : LineBackgroundSpan {
companion object {
private const val NO_INIT = -1f
}
private val rect = RectF()
private val paint = Paint().apply {
color = backgroundColor
isAntiAlias = true
}
private val path = Path()
private var prevWidth = NO_INIT
private var prevRight = NO_INIT
override fun drawBackground(
c: Canvas,
p: Paint,
left: Int,
right: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
lineNumber: Int
) {
val actualWidth = p.measureText(text, start, end) + 2f * padding
val widthDiff = abs(prevWidth - actualWidth)
val width = if (lineNumber == 0) {
actualWidth
} else if ((actualWidth < prevWidth) && (widthDiff < 2f * radius)) {
prevWidth
} else if ((actualWidth > prevWidth) && (widthDiff < 2f * radius)) {
actualWidth + (2f * radius - widthDiff)
} else {
actualWidth
}
val shiftLeft = 0f - padding
val shiftRight = width + shiftLeft
rect.set(shiftLeft, top.toFloat(), shiftRight, bottom.toFloat())
c.drawRoundRect(rect, radius, radius, paint)
if (lineNumber > 0) {
drawCornerType1(c, rect, radius)
when {
prevWidth < width -> drawCornerType2(c, rect, radius)
prevWidth > width -> drawCornerType3(c, rect, radius)
else -> drawCornerType4(c, rect, radius)
}
}
prevWidth = width
prevRight = rect.right
}
private fun drawLeftCorner(c: Canvas, rect: RectF, radius: Float) {
path.reset()
path.moveTo(rect.left, rect.top + radius)
path.lineTo(rect.left, rect.top - radius)
path.lineTo(rect.left + radius, rect.top)
path.lineTo(rect.left, rect.top + radius)
c.drawPath(path, paint)
}
private fun drawTopCorner(c: Canvas, rect: RectF, radius: Float) {
path.reset()
path.moveTo(prevRight + radius, rect.top)
path.lineTo(prevRight - radius, rect.top)
path.lineTo(prevRight, rect.top - radius)
path.cubicTo(
prevRight, rect.top - radius,
prevRight, rect.top,
prevRight + radius, rect.top
)
c.drawPath(path, paint)
}
private fun drawBottomCorner(c: Canvas, rect: RectF, radius: Float) {
path.reset()
path.moveTo(rect.right + radius, rect.top)
path.lineTo(rect.right - radius, rect.top)
path.lineTo(rect.right, rect.top + radius)
path.cubicTo(
rect.right, rect.top + radius,
rect.right, rect.top,
rect.right + radius, rect.top
)
c.drawPath(path, paint)
}
private fun drawRightCorner(c: Canvas, rect: RectF, radius: Float) {
path.reset()
path.moveTo(rect.right, rect.top - radius)
path.lineTo(rect.right, rect.top + radius)
path.lineTo(rect.right - radius, rect.top)
path.lineTo(rect.right, rect.top - radius)
c.drawPath(path, paint)
}
}
并使用它:
private fun initSpannableText() {
val span = RoundedBackgroundSpan(
backgroundColor = colors.random(),
padding = dp(5),
radius = dp(5)
)
with(spanText) {
setShadowLayer(dp(10), 0f, 0f, 0) // it's important for padding working
text = androidx.core.text.buildSpannedString { inSpans(span) { append(text.toString()) } }
}
}
有关本文实现的更多详细信息: https://medium.com/@Semper_Viventem/simple-implementation-of-rounded-background-for-text-in-android-60a7706c0419
答案 3 :(得分:0)
修改了@Rahul_Tiwari的版本,以在文本大小更改时自动缩放填充和拐角半径。它根据默认文本大小值的百分比变化来缩放。根据需要加上setShadowLayer。它还在文本的顶部和底部添加了填充,因此填充在所有面上都是相同的。
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.text.style.LineBackgroundSpan
import android.view.Gravity
import android.widget.TextView
import kotlin.math.abs
import kotlin.math.sign
class BackgroundColorSpan(private val tv: TextView,
backgroundColor: Int,
private val defaultTextSizePx: Float,
private val paddingToTextSizeRatio : Float = 0.125f,
gravityAlignment: Int = Gravity.CENTER) : LineBackgroundSpan {
private val rect = RectF()
private val paint = Paint()
private val paintStroke = Paint()
private val path = Path()
private var prevWidth = -1f
private var prevLeft = -1f
private var prevRight = -1f
private var prevBottom = -1f
private var prevTop = -1f
/***
* Gravity.CENTER_HORIZONTAL
* Gravity.LEFT
* Gravity.RIGHT
*/
private var gravityAlignment : Int
init {
tv.includeFontPadding = false
paint.color = backgroundColor
paintStroke.color = backgroundColor
this.gravityAlignment = gravityAlignment and Gravity.HORIZONTAL_GRAVITY_MASK
}
private val paddingForDefaultTextSize: Float get() = defaultTextSizePx * paddingToTextSizeRatio
private fun getTextScale(currentPaint: Paint) : Float = currentPaint.textSize / defaultTextSizePx
private fun getTagWidth(text: CharSequence, start: Int, end: Int, paint: Paint, padding: Float): Float =
padding + paint.measureText(text, start, end) + padding
private fun updatePaddingAndShadowLayerRadius(padding: Float) {
if (tv.shadowRadius != padding) {
tv.setShadowLayer(padding/* radius */, 0.toFloat(), 0.toFloat(), 0 /* transparent */)
}
val paddingI= padding.toInt()
if (tv.paddingLeft != paddingI && tv.paddingRight != paddingI){
tv.setPadding(paddingI, paddingI, paddingI, paddingI)
tv.setLineSpacing(padding, 1.0f)
}
}
override fun drawBackground(
c: Canvas,
p: Paint,
left: Int,
right: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
lnum: Int) {
val paddingForTextSize = paddingForDefaultTextSize * getTextScale(p)
updatePaddingAndShadowLayerRadius(paddingForTextSize)
val width = getTagWidth(text, start, end, p, paddingForTextSize)
val shiftLeft: Float
val shiftRight: Float
val fm = p.fontMetrics
val tagBottom: Float = baseline + fm.descent + paddingForTextSize
val topPadding = if (lnum == 0 ) paddingForTextSize else 0f
val tagTop: Float = baseline + fm.ascent - topPadding
val tagHeight = tagBottom - tagTop
val radius = tagHeight / 10
when (gravityAlignment) {
Gravity.LEFT -> {
shiftLeft = 0f - paddingForTextSize
shiftRight = width + shiftLeft
}
Gravity.RIGHT -> {
shiftLeft = right - width + paddingForTextSize
shiftRight = (right + paddingForTextSize)
}
else -> {
shiftLeft = (right - width) / 2
shiftRight = right - shiftLeft
}
}
rect.set(shiftLeft, tagTop, shiftRight, tagBottom)
if (lnum == 0) {
c.drawRoundRect(rect, radius, radius, paint)
} else {
path.reset()
val difference = width - prevWidth
val diff = -sign(difference) * (2f * radius).coerceAtMost(abs(difference / 2f)) / 2f
path.moveTo(
prevLeft, prevBottom - radius
)
if (gravityAlignment != Gravity.LEFT) {
path.cubicTo(//1
prevLeft, prevBottom - radius,
prevLeft, rect.top,
prevLeft + diff, rect.top
)
} else {
path.lineTo(prevLeft, prevBottom + radius)
}
path.lineTo(
rect.left - diff, rect.top
)
path.cubicTo(//2
rect.left - diff, rect.top,
rect.left, rect.top,
rect.left, rect.top + radius
)
path.lineTo(
rect.left, rect.bottom - radius
)
path.cubicTo(//3
rect.left, rect.bottom - radius,
rect.left, rect.bottom,
rect.left + radius, rect.bottom
)
path.lineTo(
rect.right - radius, rect.bottom
)
path.cubicTo(//4
rect.right - radius, rect.bottom,
rect.right, rect.bottom,
rect.right, rect.bottom - radius
)
path.lineTo(
rect.right, rect.top + radius
)
if (gravityAlignment != Gravity.RIGHT) {
path.cubicTo(//5
rect.right, rect.top + radius,
rect.right, rect.top,
rect.right + diff, rect.top
)
path.lineTo(
prevRight - diff, rect.top
)
path.cubicTo(//6
prevRight - diff, rect.top,
prevRight, rect.top,
prevRight, prevBottom - radius
)
} else {
path.lineTo(prevRight, prevBottom - radius)
}
path.cubicTo(//7
prevRight, prevBottom - radius,
prevRight, prevBottom,
prevRight - radius, prevBottom
)
path.lineTo(
prevLeft + radius, prevBottom
)
path.cubicTo(//8
prevLeft + radius, prevBottom,
prevLeft, prevBottom,
prevLeft, rect.top - radius
)
c.drawPath(path, paintStroke)
}
prevWidth = width
prevLeft = rect.left
prevRight = rect.right
prevBottom = rect.bottom
prevTop = rect.top
}
}