Android中TextView限制最大行數(shù)并在最后用顯示...全文
一、場(chǎng)景
我們知道通常在列表頁(yè)面會(huì)有很多內(nèi)容,而且每條內(nèi)容可能會(huì)很長(zhǎng),如果每條內(nèi)容都全部顯示用戶(hù)體驗(yàn)就很不好。所以,我們通常的處理方案是限制每條內(nèi)容的行數(shù),這個(gè)時(shí)候如果想更加明顯的提示用戶(hù)該條內(nèi)容有更多的內(nèi)容,可以進(jìn)入詳情頁(yè)查看時(shí)會(huì)在內(nèi)容最后加上“全文”之類(lèi)的字眼。尤其是社區(qū)內(nèi)的APP里經(jīng)常會(huì)看到這樣的場(chǎng)景,比如:微博。
二、方案的實(shí)現(xiàn)
那如果我們想限制最大行數(shù)且在最后顯示...全文該怎么實(shí)現(xiàn)呢?我們知道我們通常設(shè)置TextView的最大行數(shù)是設(shè)置maxLines屬性,并設(shè)置android:ellipsize="end"表示在內(nèi)容最后顯示...。但是類(lèi)似"全文“這樣的文字怎么顯示呢?我想這時(shí)大家肯定會(huì)想到:在內(nèi)容最后拼上去??!沒(méi)錯(cuò),是需要拼上去,那要怎么拼?怎么拼上去正好在內(nèi)容的最后,既不提前、又完整顯示”全文“?
1、”常規(guī)”方案
網(wǎng)上大多關(guān)于這個(gè)需求的實(shí)現(xiàn)方案都是在textView.setText()之后調(diào)用textView.post方法,偽代碼:
textView.post(new Runnable() {
@Override
public void run() {
//進(jìn)行內(nèi)容的截取和拼接
}
});或者是設(shè)置addOnGlobalLayoutListener監(jiān)聽(tīng),偽代碼:
textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//進(jìn)行內(nèi)容的截取和拼接
}
}
});其本質(zhì)和核心都是為了獲取內(nèi)容的行數(shù),來(lái)判斷是否大于我們想設(shè)置的最大行數(shù),來(lái)進(jìn)行內(nèi)容的截取和”全文“的拼接。
但是該方案是在setText()之后進(jìn)行的截取,也就是TextView已經(jīng)顯示了內(nèi)容然后再進(jìn)行內(nèi)容的處理再次setText()。那么會(huì)有以下明顯的缺點(diǎn):
1:在性能差的設(shè)備上會(huì)有閃現(xiàn)全部?jī)?nèi)容然后再顯示處理后的內(nèi)容。
2:這樣做會(huì)有兩次的setText()操作,在內(nèi)容很多的列表頁(yè)會(huì)加大性能的損耗。
2、"優(yōu)化"的處理方案
這個(gè)時(shí)候可能有人會(huì)說(shuō)既然繪制完成后再處理會(huì)有問(wèn)題,提前獲取到textView的行數(shù)進(jìn)行處理不就好了嗎?沒(méi)錯(cuò),我們可以設(shè)置addOnPreDrawListener監(jiān)聽(tīng)提前獲取行數(shù)來(lái)進(jìn)行處理,偽代碼:
textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw() {
//進(jìn)行內(nèi)容的截取和拼接
return false
}
});但是這種方案只適合單獨(dú)一條內(nèi)容,不適合在列表中使用,因?yàn)檫@樣只有在第一屏有效,且滑動(dòng)多屏后回到第一屏也會(huì)重置為原始數(shù)據(jù)。
3、最終方案
既然設(shè)置addOnPreDrawListener監(jiān)聽(tīng)提前獲取行數(shù)來(lái)進(jìn)行處理的方案在列表中不可行還有沒(méi)有其他方法呢?那當(dāng)然是在TextView的onMeasure()中測(cè)量textView的高度時(shí)進(jìn)行內(nèi)容的處理,并設(shè)置相對(duì)應(yīng)的高度了,這樣就可以保證性能問(wèn)題又能保證列表中的每條內(nèi)容都能得到處理。先上代碼:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (lineCount > maxLine) {
//如果大于設(shè)置的最大行數(shù)
val (layout, stringBuilder, sb) = clipContent()
stringBuilder.append(sb)
setMeasuredDimension(measuredWidth, getDesiredHeight(layout))
text = stringBuilder
}
}
/**
* 裁剪內(nèi)容
*/
private fun clipContent(): Triple<Layout, SpannableStringBuilder, SpannableString> {
var offset = 1
val layout = layout
val staticLayout = StaticLayout(
text,
layout.paint,
layout.width,
Layout.Alignment.ALIGN_NORMAL,
layout.spacingMultiplier,
layout.spacingAdd,
false
)
val indexEnd = staticLayout.getLineEnd(maxLine - 1)
val tempText = text.subSequence(0, indexEnd)
var offsetWidth =
layout.paint.measureText(tempText[indexEnd - 1].toString()).toInt()
val moreWidth =
ceil(layout.paint.measureText(moreText).toDouble()).toInt()
//表情字節(jié)個(gè)數(shù)
var countEmoji = 0
while (indexEnd > offset && offsetWidth <= moreWidth ) {
//當(dāng)前字節(jié)是否位表情
val isEmoji = PublicMethod.isEmojiCharacter(tempText[indexEnd - offset])
if (isEmoji){
countEmoji += 1
}
offset++
val pair = getOffsetWidth(
indexEnd,
offset,
tempText,
countEmoji,
offsetWidth,
layout,
moreWidth
)
offset = pair.first
offsetWidth = pair.second
}
val ssbShrink = tempText.subSequence(0, indexEnd - offset)
val stringBuilder = SpannableStringBuilder(ssbShrink)
val sb = SpannableString(moreText)
sb.setSpan(
ForegroundColorSpan(moreTextColor), 3, sb.length,
Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
//設(shè)置字體大小
sb.setSpan(
AbsoluteSizeSpan(moreTextSize, true), 3, sb.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (moreCanClick){
//設(shè)置點(diǎn)擊事件
sb.setSpan(
MyClickSpan(context, onAllSpanClickListener), 3, sb.length,
Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
}
return Triple(layout, stringBuilder, sb)
}
private fun getOffsetWidth(
indexEnd: Int,
offset: Int,
tempText: CharSequence,
countEmoji: Int,
offsetWidth: Int,
layout: Layout,
moreWidth: Int
): Pair<Int, Int> {
var offset1 = offset
var offsetWidth1 = offsetWidth
if (indexEnd > offset1) {
val text = tempText[indexEnd - offset1 - 1].toString().trim()
if (text.isNotEmpty() && countEmoji % 2 == 0) {
val charText = tempText[indexEnd - offset1]
offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
//一個(gè)表情兩個(gè)字符,避免截取一半字符出現(xiàn)亂碼或者顯示不全...全文
if (offsetWidth1 > moreWidth && PublicMethod.isEmojiCharacter(charText)) {
offset1++
}
}
} else {
val charText = tempText[indexEnd - offset1]
offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
}
return Pair(offset1, offsetWidth1)
}
/**
* 獲取內(nèi)容高度
*/
private fun getDesiredHeight(layout: Layout?): Int {
if (layout == null) {
return 0
}
val lineTop: Int
val lineCount = layout.lineCount
val compoundPaddingTop = compoundPaddingTop + compoundPaddingBottom - lineSpacingExtra.toInt()
lineTop = when {
lineCount > maxLine -> {
//文字行數(shù)超過(guò)最大行
layout.getLineTop(maxLine)
}
else -> {
layout.getLineTop(lineCount)
}
}
return (lineTop + compoundPaddingTop).coerceAtLeast(suggestedMinimumHeight)
}大概思路就是判斷內(nèi)容行數(shù)大于我們想要的內(nèi)容行數(shù)時(shí)進(jìn)行內(nèi)容的裁剪,內(nèi)容最后顯示的文案moreText可以按照需求配置,我們測(cè)量出moreText的寬度,從最大行數(shù)的最后一個(gè)文字向前遍歷截取,直至截取文字的寬度大于等于moreText的寬度,然后我們通過(guò)使用SpannableString來(lái)拼接moreText文案和moreText的點(diǎn)擊事件。這里還處理了截取到表情字符的情況,我們知道一個(gè)表情兩個(gè)字符,如果正好截取到表情的一半可以放下moreText就會(huì)導(dǎo)致表情變成一個(gè)?的亂碼。 另外這里,我們?cè)O(shè)置了moreText的點(diǎn)擊事件,那如果textView本身需要設(shè)置點(diǎn)擊事件怎么辦?這個(gè)時(shí)候就需要處理觸摸事件了,代碼如下:
val text = text
val spannable = Spannable.Factory.getInstance().newSpannable(text)
if (event.action == MotionEvent.ACTION_DOWN) {
//手指按下
onDown(spannable, event)
}
if (mPressedSpan != null && mPressedSpan is MyLinkClickSpan) {
//如果有MyLinkClickSpan就走M(jìn)yLinkMovementMethod的onTouchEvent
return MyLinkMovementMethod.instance
.onTouchEvent(this, text as Spannable, event)
}
if (event.action == MotionEvent.ACTION_MOVE) {
//手指移動(dòng)
val mClickSpan = getPressedSpan(this, spannable, event)
if (mPressedSpan != null && mPressedSpan !== mClickSpan) {
mPressedSpan = null
Selection.removeSelection(spannable)
}
}
if (event.action == MotionEvent.ACTION_UP) {
//手指抬起
onUp(event, spannable)
}
return result
}
/**
* 手指按下邏輯
*/
private fun onDown(spannable: Spannable, event: MotionEvent) {
//按下時(shí)記下clickSpan
mPressedSpan = getPressedSpan(this, spannable, event)
if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
result = true
Selection.setSelection(
spannable, spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan)
)
} else {
result = if (moreCanClick){
super.onTouchEvent(event)
}else{
false
}
}
}
/**
* 手指抬起邏輯
*/
private fun onUp(event: MotionEvent, spannable: Spannable?) {
result = if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
(mPressedSpan as MyClickSpan).onClick(this)
true
} else {
if (moreCanClick) {
super.onTouchEvent(event)
}
false
}
mPressedSpan = null
Selection.removeSelection(spannable)
}
/**
* 設(shè)置尾部...全文點(diǎn)擊事件
*/
fun setOnAllSpanClickListener(
onAllSpanClickListener: MyClickSpan.OnAllSpanClickListener
) {
this.onAllSpanClickListener = onAllSpanClickListener
}
private fun getPressedSpan(
textView: TextView, spannable: Spannable,
event: MotionEvent
): ClickableSpan? {
var mTouchSpan: ClickableSpan? = null
var x = event.x.toInt()
var y = event.y.toInt()
x -= textView.totalPaddingLeft
x += textView.scrollX
y -= textView.totalPaddingTop
y += textView.scrollY
val layout = layout
val line = layout.getLineForVertical(y)
val off = layout.getOffsetForHorizontal(line, x.toFloat())
val spans: Array<MyClickSpan> =
spannable.getSpans(
off, off,
MyClickSpan::class.java
)
if (spans.isNotEmpty()) {
mTouchSpan = spans[0]
} else {
val linkSpans = spannable.getSpans(off, off, MyLinkClickSpan::class.java)
if (linkSpans != null && linkSpans.isNotEmpty()) {
mTouchSpan = linkSpans[0]
}
}
return mTouchSpan
}其中 if (mPressedSpan != null && mPressedSpan is MyLinkClickSpan) { //如果有MyLinkClickSpan就走M(jìn)yLinkMovementMethod的onTouchEvent return MyLinkMovementMethod.instance .onTouchEvent(this, text as Spannable, event) }是對(duì)鏈接的兼容處理,如果對(duì)這個(gè)有疑問(wèn)請(qǐng)看我的上一篇關(guān)于鏈接描述的文章#Android 仿微博正文鏈接交互
三、完整代碼
class ListMoreTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.MoreTextViewStyle
) :
AppCompatTextView(context, attrs, defStyleAttr) {
/**
* 最大行數(shù)
*/
private var maxLine: Int
private val moreTextSize: Int
/**
* 尾部更多文字
*/
private val moreText: String?
/**
* 尾部更多文字顏色
*/
private val moreTextColor: Int
/**
* 是否可以點(diǎn)擊尾部更多文字
*/
private val moreCanClick : Boolean
private var mPaint: Paint? = null
/**
* 尾部更多文字點(diǎn)擊事件接口回調(diào)
*/
private var onAllSpanClickListener: MyClickSpan.OnAllSpanClickListener? = null
/**
* 實(shí)現(xiàn)span的點(diǎn)擊
*/
private var mPressedSpan: ClickableSpan? = null
private var result = false
init {
val array = getContext().obtainStyledAttributes(
attrs,
R.styleable.ListMoreTextView, defStyleAttr, 0
)
maxLine = array.getInt(R.styleable.MoreTextView_more_action_text_maxLines, Int.MAX_VALUE)
moreText = array.getString(R.styleable.MoreTextView_more_action_text)
moreTextSize = array.getInteger(R.styleable.MoreTextView_more_action_text_size, 13)
moreTextColor = array.getColor(R.styleable.MoreTextView_more_action_text_color, Color.BLACK)
moreCanClick = array.getBoolean(R.styleable.MoreTextView_more_can_click,false)
array.recycle()
init()
}
private fun init() {
mPaint = paint
}
/**
* 設(shè)置最大行數(shù)
*/
fun setMaxLine (maxLine : Int){
this.maxLine = maxLine
}
/**
* 使用者主動(dòng)調(diào)用
* 如果有顯示鏈接需求一定要調(diào)用此方法
*/
fun setMovementMethodDefault() {
movementMethod = MyLinkMovementMethod.instance
highlightColor = Color.TRANSPARENT
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (lineCount > maxLine) {
//如果大于設(shè)置的最大行數(shù)
val (layout, stringBuilder, sb) = clipContent()
stringBuilder.append(sb)
setMeasuredDimension(measuredWidth, getDesiredHeight(layout))
text = stringBuilder
}
}
/**
* 裁剪內(nèi)容
*/
private fun clipContent(): Triple<Layout, SpannableStringBuilder, SpannableString> {
var offset = 1
val layout = layout
val staticLayout = StaticLayout(
text,
layout.paint,
layout.width,
Layout.Alignment.ALIGN_NORMAL,
layout.spacingMultiplier,
layout.spacingAdd,
false
)
val indexEnd = staticLayout.getLineEnd(maxLine - 1)
val tempText = text.subSequence(0, indexEnd)
var offsetWidth =
layout.paint.measureText(tempText[indexEnd - 1].toString()).toInt()
val moreWidth =
ceil(layout.paint.measureText(moreText).toDouble()).toInt()
//表情字節(jié)個(gè)數(shù)
var countEmoji = 0
while (indexEnd > offset && offsetWidth <= moreWidth ) {
//當(dāng)前字節(jié)是否位表情
val isEmoji = PublicMethod.isEmojiCharacter(tempText[indexEnd - offset])
if (isEmoji){
countEmoji += 1
}
offset++
val pair = getOffsetWidth(
indexEnd,
offset,
tempText,
countEmoji,
offsetWidth,
layout,
moreWidth
)
offset = pair.first
offsetWidth = pair.second
}
val ssbShrink = tempText.subSequence(0, indexEnd - offset)
val stringBuilder = SpannableStringBuilder(ssbShrink)
val sb = SpannableString(moreText)
sb.setSpan(
ForegroundColorSpan(moreTextColor), 3, sb.length,
Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
//設(shè)置字體大小
sb.setSpan(
AbsoluteSizeSpan(moreTextSize, true), 3, sb.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (moreCanClick){
//設(shè)置點(diǎn)擊事件
sb.setSpan(
MyClickSpan(context, onAllSpanClickListener), 3, sb.length,
Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
}
return Triple(layout, stringBuilder, sb)
}
private fun getOffsetWidth(
indexEnd: Int,
offset: Int,
tempText: CharSequence,
countEmoji: Int,
offsetWidth: Int,
layout: Layout,
moreWidth: Int
): Pair<Int, Int> {
var offset1 = offset
var offsetWidth1 = offsetWidth
if (indexEnd > offset1) {
val text = tempText[indexEnd - offset1 - 1].toString().trim()
if (text.isNotEmpty() && countEmoji % 2 == 0) {
val charText = tempText[indexEnd - offset1]
offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
//一個(gè)表情兩個(gè)字符,避免截取一半字符出現(xiàn)亂碼或者顯示不全...全文
if (offsetWidth1 > moreWidth && PublicMethod.isEmojiCharacter(charText)) {
offset1++
}
}
} else {
val charText = tempText[indexEnd - offset1]
offsetWidth1 += layout.paint.measureText(charText.toString()).toInt()
}
return Pair(offset1, offsetWidth1)
}
/**
* 獲取內(nèi)容高度
*/
private fun getDesiredHeight(layout: Layout?): Int {
if (layout == null) {
return 0
}
val lineTop: Int
val lineCount = layout.lineCount
val compoundPaddingTop = compoundPaddingTop + compoundPaddingBottom - lineSpacingExtra.toInt()
lineTop = when {
lineCount > maxLine -> {
//文字行數(shù)超過(guò)最大行
layout.getLineTop(maxLine)
}
else -> {
layout.getLineTop(lineCount)
}
}
return (lineTop + compoundPaddingTop).coerceAtLeast(suggestedMinimumHeight)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val text = text
val spannable = Spannable.Factory.getInstance().newSpannable(text)
if (event.action == MotionEvent.ACTION_DOWN) {
//手指按下
onDown(spannable, event)
}
if (mPressedSpan != null && mPressedSpan is MyLinkClickSpan) {
//如果有MyLinkClickSpan就走M(jìn)yLinkMovementMethod的onTouchEvent
return MyLinkMovementMethod.instance
.onTouchEvent(this, text as Spannable, event)
}
if (event.action == MotionEvent.ACTION_MOVE) {
//手指移動(dòng)
val mClickSpan = getPressedSpan(this, spannable, event)
if (mPressedSpan != null && mPressedSpan !== mClickSpan) {
mPressedSpan = null
Selection.removeSelection(spannable)
}
}
if (event.action == MotionEvent.ACTION_UP) {
//手指抬起
onUp(event, spannable)
}
return result
}
/**
* 手指按下邏輯
*/
private fun onDown(spannable: Spannable, event: MotionEvent) {
//按下時(shí)記下clickSpan
mPressedSpan = getPressedSpan(this, spannable, event)
if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
result = true
Selection.setSelection(
spannable, spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan)
)
} else {
result = if (moreCanClick){
super.onTouchEvent(event)
}else{
false
}
}
}
/**
* 手指抬起邏輯
*/
private fun onUp(event: MotionEvent, spannable: Spannable?) {
result = if (mPressedSpan != null && mPressedSpan is MyClickSpan) {
(mPressedSpan as MyClickSpan).onClick(this)
true
} else {
if (moreCanClick) {
super.onTouchEvent(event)
}
false
}
mPressedSpan = null
Selection.removeSelection(spannable)
}
/**
* 設(shè)置尾部...全文點(diǎn)擊事件
*/
fun setOnAllSpanClickListener(
onAllSpanClickListener: MyClickSpan.OnAllSpanClickListener
) {
this.onAllSpanClickListener = onAllSpanClickListener
}
private fun getPressedSpan(
textView: TextView, spannable: Spannable,
event: MotionEvent
): ClickableSpan? {
var mTouchSpan: ClickableSpan? = null
var x = event.x.toInt()
var y = event.y.toInt()
x -= textView.totalPaddingLeft
x += textView.scrollX
y -= textView.totalPaddingTop
y += textView.scrollY
val layout = layout
val line = layout.getLineForVertical(y)
val off = layout.getOffsetForHorizontal(line, x.toFloat())
val spans: Array<MyClickSpan> =
spannable.getSpans(
off, off,
MyClickSpan::class.java
)
if (spans.isNotEmpty()) {
mTouchSpan = spans[0]
} else {
val linkSpans = spannable.getSpans(off, off, MyLinkClickSpan::class.java)
if (linkSpans != null && linkSpans.isNotEmpty()) {
mTouchSpan = linkSpans[0]
}
}
return mTouchSpan
}
}<declare-styleable name="ListMoreTextView">
<attr name="more_action_text_maxLines" format="integer"/>
<attr name="more_action_text" format="string"/>
<attr name="more_action_text_color" format="color"/>
<attr name="more_action_text_size" format="integer"/>
<attr name="more_can_click" format="boolean"/>
</declare-styleable>注意:如果是有鏈接需求要主動(dòng)調(diào)用該方法,否則鏈接的觸摸交互無(wú)效。
/**
* 使用者主動(dòng)調(diào)用
* 如果有顯示鏈接需求一定要調(diào)用此方法
*/
fun setMovementMethodDefault() {
movementMethod = MyLinkMovementMethod.instance
highlightColor = Color.TRANSPARENT
}另外,這里沒(méi)有對(duì)內(nèi)容連續(xù)換行的處理,因?yàn)閭€(gè)人覺(jué)得列表數(shù)據(jù)是對(duì)主要內(nèi)容的顯示,另外客戶(hù)端不要做太多的數(shù)據(jù)處理的耗時(shí)操作,應(yīng)該是由后端的同學(xué)或者產(chǎn)品設(shè)計(jì)時(shí)避免這種情況的產(chǎn)生。
四、效果

五、代碼地址
作者:笑慢
鏈接:https://juejin.cn/post/7037416456782348295
來(lái)源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
相關(guān)文章
Android ListView實(shí)現(xiàn)仿iPhone實(shí)現(xiàn)左滑刪除按鈕的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Android ListView實(shí)現(xiàn)仿iPhone實(shí)現(xiàn)左滑刪除按鈕的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
詳談OnTouchListener與OnGestureListener的區(qū)別
下面小編就為大家?guī)?lái)一篇詳談OnTouchListener與OnGestureListener的區(qū)別。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
Android 中無(wú)法取消標(biāo)題欄的問(wèn)題小結(jié)(兩種方法)
我們都知道取消標(biāo)題欄有兩種方式,一種是在Java代碼中取消,另一種通過(guò)設(shè)置styles.xml文件中的Theme即可,下面就兩種方法給大家簡(jiǎn)答介紹下2016-12-12
Kotlin開(kāi)發(fā)筆記之委托屬性與區(qū)間(譯)
最近在學(xué)習(xí)kotlin,發(fā)現(xiàn)了一些比較重要的知識(shí)點(diǎn),所以下面這篇文章主要給大家介紹了關(guān)于Kotlin開(kāi)發(fā)筆記之委托屬性與區(qū)間的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-12-12
android基礎(chǔ)教程之a(chǎn)ndroid的listview與edittext沖突解決方法
這篇文章主要介紹了android的listview與edittext沖突解決方法,需要的朋友可以參考下2014-02-02

