Android Notes|实用小技巧,不定期更新...

885 阅读5分钟

还是老规矩,小图镇楼~

image.png

小厂开发而言,日常的繁杂的业务开发任务相对较重,有些东西,首次遇到,后续也不想再浪费时间。

一直想做一个积累,一个笔记,一个总结,将日常开发中遇到的小细节记录在案,方便查阅,也能方便帮助其他小伙伴~

还是没等到掘金上线图片水印开关,先发文,后更新啦~

有不对地方欢迎大佬指点~

View 篇章

Shape

layer-list 定义图片

假设 UI 仅提供中间沙发 logo,如何构建如下 UI 效果图?

image.png

首先构建外层圆形背景图:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size
        android:width="@dimen/dimen_90_dp"
        android:height="@dimen/dimen_90_dp" />
    <solid android:color="@color/color_2d0d52" />
</shape>

合并生成一张图:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/shape_circle_2d0d52" />
    <item
        android:drawable="@drawable/ic_guard_sofa"
        android:gravity="center" />
</layer-list>

shape 搭配 layer-list 实现边框样式

文字描述不是很清晰,直接看效果图吧:

image.png

注意观察,只有底部没有边框~

先实现底层 shape:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="@dimen/dimen_20_dp" />
    <gradient
        android:angle="120"
        android:endColor="@color/color_cf77de_00"
        android:startColor="@color/color_b573d8"
        android:type="linear" />
</shape>

效果如下:

image.png

随后编辑上层 shape:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="@dimen/dimen_20_dp" />
    <gradient
        android:angle="180"
        android:endColor="@color/color_5a13a2"
        android:startColor="@color/color_6f1e94"
        android:type="linear" />
</shape>

效果如下:

image.png

两者结合,孕育新生:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/shape_b573d8_00cf77de" />
    <item
        android:bottom="-2dp"
        android:drawable="@drawable/shape_6f1e94_5a13a2"
        android:left="@dimen/dimen_1_dp"
        android:right="@dimen/dimen_1_dp"
        android:top="@dimen/dimen_1_dp" />
</layer-list>

ConstraintLayout

动态设置约束(适用于需要调整单个情况)

fun adjustKtvImChatInParentRoot(imChatView: View) {
    val layoutParams = imChatView.layoutParams
    if (layoutParams is ConstraintLayout.LayoutParams) {
        layoutParams.reset()  // 重置布局参数
        layoutParams.topToTop = R.id.imChatSpace
        layoutParams.bottomToBottom = R.id.imChatSpace
        layoutParams.startToEnd = R.id.imChatSpace
    }
}

动态设置约束(适用于需要调整多个情况)

fun resetVIPPosition(csRoot: ConstraintLayout) = ConstraintSet().apply {  // 动态设置约束的合集
    clone(csRoot)   // 克隆父布局约束
    clear(R.id.vip_view_fl, END)   // 移除指定约束
    connect(R.id.vip_view_fl, TOP, R.id.view_flipper, BOTTOM, 10.dp)   // top_bottom margin 10
}.applyTo(csRoot)                       // 应用约束

简单说下步骤吧:

  • 首先通过 clone 进行父布局克隆;
  • 随后可以通过 clear 清除 view 指定约束,前者为 view id,后者为约束位置,上右下左;
  • 最后可以通过 connect 设置对应的约束即可,参数依次为:当前 view id,对其约束位置,目标 view id,对其约束位置,margin

动态设置比例

xml 布局中通过如下设置比例:

app:layout_constraintDimensionRatio="900:1170"

代码中则可以通过如下方式:

ConstraintSet().apply {
    clone(prettyParentCl)   <--- 根布局 id
    setDimensionRatio(
        R.id.buyPrettyNumIv,  <--- 要调整的 View Id
        if (mIsBindPrettyNum) "900:990" else "900:1170"  <--- 调整后的比例
    )
}.applyTo(prettyParentCl)   <--- 根布局 id

TextView

内部可滑动

xml 设置滑动方向:

android:scrollbars="vertical"

代码中设置 mode:

textView.movementMethod = ScrollingMovementMethod.getInstance()

一个 TextView 实现 drawable + 文字效果

如下图所示:

image.png

实现重点:

  • TextView 宽度自适应;
  • 设置 DrawPadding;
  • 设置 Padding

代码设置加粗

findViewById<TextView>(R.id.comm_indicator_txt).apply {
    paint.isFakeBoldText = true     // 加粗
}

代码设置 selector color

setTextColor(ContextCompat.getColorStateList(context, [selector resid]))

代码设置 drawable/LTRB/Padding

Way 1: 通过 setCompoundDrawables 方式

private fun TextView.setCardOperateDrawable(resId: Int) {
    val drawable = ContextCompat.getDrawable(mContext, resId)?.apply {
        // 必须设置 Bounds 否则 drawable 不显示
        setBounds(0, 0, minimumWidth, minimumHeight)
    } 
    compoundDrawablePadding = drawablePadding 
    setCompoundDrawables(drawable, null, null, null)
}

Way 2: 通过 setCompoundDrawablesRelativeWithIntrinsicBounds 方式

sudGameOperatorAudioTxt.apply {
    // ...
    setCompoundDrawablesRelativeWithIntrinsicBounds(
        if (mAudioOperatorStatus) R.drawable.xxx else R.drawable.xxx,
        0,
        0,
        0
    )
}

代码设置 drawable 并指定宽高

公共方法抽离:

fun getDrawable(context: Context, resId: Int, right: Int = -1, bottom: Int = -1): Drawable? =  
    ContextCompat.getDrawable(context, resId)?.apply {  
    // 必须设置 Bounds 否则 drawable 不显示  
    setBounds(  
    0,  
    0,  
    if (right == -1) minimumWidth else right,  
    if (bottom == -1) minimumHeight else bottom  
    )  
}

具体设置案例:

private fun resetStatusRes(filterTxt: TypeFontTextView, drawableRes: Int = -1) {  
    val defaultRes = if (drawableRes == -1) R.drawable.ic_filter_nor else drawableRes  
    val iconDrawable = getDrawable(mContext, defaultRes, 10.dp, 10.dp)  
    filterTxt.setCompoundDrawables(null, null, iconDrawable, null)  
}

在文字后追加一个图片

binding.contractDetailOathTxt.apply {  
    val oathStr = ""
    val spannableString = SpannableString(oathStr)  
    val drawable = resources.getDrawable(R.drawable.icon_edit_white, null)  
    val resizedBitmap = resizeBitmap(drawableToBitmap(drawable), 14.dp, 14.dp)  
    val imageSpan =  
        ImageSpan(context, resizedBitmap, DynamicDrawableSpan.ALIGN_BOTTOM)  
    spannableString.setSpan(  
        imageSpan,  
        oathStr.length - 1,  
        oathStr.length,  
        Spannable.SPAN_INCLUSIVE_EXCLUSIVE  
    )  
    text = spannableString
}

设置文字竖向展示

android:ems="1"

ems: 代表当前一行可设置几个字符。

文字超链接点击后,背景色如何消除

highlightColor = R.color.trans.ColorInt

跑马灯效果

xml 布局中设置如下:(注意宽度要限制)

android:ellipsize="marquee" 
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"

代码中设置 isSelected = true 即可。

RecyclerView

多点触控导致 item 多次触发

大概的就是就是,因为多点触控的关系,导致多个手指点击后,item 执行了多个操作(可能描述不太准确)。

解决方案:

  • 给 RecycerView 添加 android:splitMotionEvents="false"

禁止滑动阴影

android:overScrollMode="never"

监听滑动并获取当前 item 位置(针对滑动)

addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
        if (recyclerView.scrollState == SCROLL_STATE_IDLE) {
            (recyclerView.layoutManager as Your Use LayoutManager).findFirstVisibleItemPosition() 
        }
    }
})

EditText

限制输入字符/字节数量

一直使用的 length 长度,便是实际输入长度,而这里的字符数,则代表字节数。

例如 GBK 编码下一个汉字占据两个字节,而 UTF-8 编码下一个汉字占据三个字节。

用户输入的字节长度,可以用以下方式获取:

  • inputStr?.toByteArray(Charset.forName("GBK")).size

下面贴出关键代码:

private val mNikeNameTextWatcher = object : TextWatcherAdapter {
    override fun afterTextChanged(s: Editable?) {
        super.afterTextChanged(s)
        et_content.getEditTextView().removeTextChangedListener(this)
        val inputStr = s?.toString()?.trim()
        val bytes = inputStr?.toByteArray(Charset.forName("GBK"))
        if ((bytes?.size ?: 0) > 16) {
            toast("超出规定字符数,请按要求填写")
            bytes?.let {
                val newBytes = ByteArray(16)
                System.arraycopy(it, 0, newBytes, 0, newBytes.size)
                val resultText = String(newBytes, Charset.forName("GBK"))
                et_content.setEditTextContent(resultText)
                Selection.setSelection(
                    et_content.getEditTextView().editableText,
                    resultText.length
                )
            }
        }
        et_content.getEditTextView().addTextChangedListener(this)
    }
}

设置密码可见/隐藏

密码明文展示:

setTransformationMethod(HideReturnsTransformationMethod.getInstance())

密码脱敏展示:

setTransformationMethod(PasswordTransformationMethod.getInstance())

记得更新光标位置:

setSelection(mEtCode.getText().length())

ViewPager2

禁止滑动

isUserInputEnabled = false

ScrollView

滚动到底部

roleScrollView.post {
    roleScrollView.fullScroll(View.FOCUS_DOWN)
}

Button

取消默认英文大写

android:textAllCaps="false"

Toolbar

取消左侧默认边距

app:contentInsetLeft="@dimen/dimen_0_dp"
app:contentInsetStart="@dimen/dimen_0_dp"

LottieAnimationView

设置默认播放进度

loadingLottie.progress = 0.5f

AppBarLayout

取消底部阴影

app:elevation="0dp"

注意是 app 下,并不是 android 下!!!

Style

style 不支持自定义属性或者非 android 命名空间抽离,如何处理?

直接在 name 中写对应的属性即可。

<style name="commUserProfileSeeMoreStyle">   
    <item name="drawableRightCompat">@drawable/icon_arrow_right_gray</item>  
</style>

无需申请权限开启震动反馈

view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)

日常开发小细节

金额超限,显示科学计数法,如何处理?

金额类型替换为 BigDecimal

例 1:A 除以 B 并保留两位小数

money.divide(BigDecimal(100), 2, BigDecimal.ROUND_DOWN).toString(), // 金额 元 

Error 汇总

java.lang.IllegalArgumentException: Parameter type must not include a type variable or wildcard:...(parameter #1)

Service 接口方法上追加 @JvmSuppressWildcards