欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

China Traditional Colors Inspired APP: "原色"

最编程 2024-08-14 10:29:47
...

中国传统色

简介

这是一个工具类APP

  • 颜色筛选以及关模糊查询
  • 颜色详情信息查看以及复制
  • 色卡分享
  • 自定义主题色(长按色卡)
  • 小组件支持

已上架应用宝/App Store,搜索原色即可找到

最初是做了个1.0版本(MVP),功能比较简单,后面感觉没什么可加的就放置一边了

1.0版本.jpeg

最近比较空闲又拿起来,bug修一点加一点,界面改了又改哈哈哈,然后现在迭代到2.0版本(预览图为 iOS)

2.0版本.jpeg 除了界面大换新,也增加了一些功能,比如颜色搜索、筛选、小组件等。Android与iOS基本一致,除了搜索筛选界面不一样:

Android搜索筛选.jpg

下面介绍一下一些功能的实现以及碰到的问题

色卡与文字处理

在1.0版本对色卡的背景颜色和文字颜色关系处理比较粗暴简单,当系统出去浅色模式下。文字就在原来颜色的基础上降低亮度;在深色模式下文字就降低亮度,但是这种方式在部分过亮或者过暗背景上还是很难看清。
2.0版本对色卡和文字颜色都做了动态处理:
色卡:渐变处理,从上往下,比例为0——0.3——1.0。

在浅色模式下颜色为color(alpha=0.7)——color——color;

在深色模式下颜色为color(brightness + 0.2)——color——color

色卡文字:根据颜色是否为亮色进行处理,判断规则为:

颜色为亮色,则降低0.3亮度,否则 降低0.1亮度

在iOS上有用于修改view亮度的方法:brightness(Double),可惜安卓没有直接修改视图或者颜色亮度的方法,于是我就通过修改颜色 HSL来达到类似的效果。为了和ios的brightness 一致,changeBrightness的范围我设置为[-1F, 1F],但outHsl[2]的范围是[0F, 1F],所以计算做了一些调整:

// 修改颜色亮度
@ColorInt
fun @receiver:ColorInt Int.brightness(changeBrightness: Float): Int {
    val outHsl = FloatArray(3)
    ColorUtils.colorToHSL(this, outHsl)
    if (changeBrightness <= 0) {
        outHsl[2] = outHsl[2] * (1 + changeBrightness)
    } else {
        outHsl[2] =  outHsl[2] + (1 - outHsl[2]) / 10 * changeBrightness * 10
    }
    return ColorUtils.HSLToColor(outHsl)
}
// 判断颜色为两色或者暗色
fun @receiver:ColorInt Int.isLight(): Boolean {
    val red = Color.valueOf(this).red()
    val green = Color.valueOf(this).green()
    val blue = Color.valueOf(this).blue()
    val brightness = (red * 299 + green * 587 + blue * 114) / 1000
    return brightness > 0.5
}

颜色信息展示(BottomSheet)

设置BottomSheet默认完全展开,设置方法如下:

override fun onStart() {
    super.onStart()
    val behavior = BottomSheetBehavior.from(requireView().parent as View)
    behavior.state = BottomSheetBehavior.STATE_EXPANDED
}

至于圆角处理,只需要在主题文件里写好就行了:

 <!--Rounded Bottom Sheet-->
<style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
    <item name="bottomSheetStyle">@style/ModalBottomSheetDialog</item>
</style>

<style name="ModalBottomSheetDialog" parent="Widget.Material3.BottomSheet.Modal">
    <item name="shapeAppearance">@style/ShapeAppearance.App.LargeComponent</item>
    <item name="shouldRemoveExpandedCorners">false</item>
</style>

<style name="ShapeAppearance.App.LargeComponent" parent="ShapeAppearance.Material3.LargeComponent">
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">24dp</item>
</style>

如果不修改sheet背景色(默认为白色/黑色),只需要设置以上主题就可以了,但是如果修改了背景色,就需要在代码里对背景进行圆角处理,不能直接设置背景色,不然在圆角下面还会有颜色:

默认设置圆角背景.png

动态设置圆角背景.png

// 会存在背景色
// binding.bottomSheetLayout.setBackgroundColor(sheetBackground)

// 设置圆角背景
binding.bottomSheetLayout.setCornerBackground(24, 24, 0, 0, sheetBackground)

private fun View.setCornerBackground(leftRadius: Int, topRadius: Int, rightRadius: Int, bottomRadius: Int, @ColorInt color: Int) {
    val shape = ShapeDrawable(RoundRectShape(
        floatArrayOf(
            leftRadius.dp(requireContext()).toFloat(),
            leftRadius.dp(requireContext()).toFloat(),
            topRadius.dp(requireContext()).toFloat(),
            topRadius.dp(requireContext()).toFloat(),
            rightRadius.dp(requireContext()).toFloat(),
            rightRadius.dp(requireContext()).toFloat(),
            bottomRadius.dp(requireContext()).toFloat(),
            bottomRadius.dp(requireContext()).toFloat(),
        ), null, null)
    )
    shape.paint.color = color
    this.background = shape
}

fun Int.dp(context: Context): Int {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this.toFloat(),
        context.resources.displayMetrics
    ).toInt()
}

小技巧(应该算啊吧):当我们有icon需要适配深色模式的时候,可以把android:tint的值设置为?android:attr/textColorPrimary ,就不用自己做额外处理了

<vector android:autoMirrored="true" android:height="24dp"
    android:tint="?android:attr/textColorPrimary" android:viewportHeight="24"
    android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

SearchView背景色修改

可以看之前发的文章

MD3——SearchView自定义背景

效果参考上面的搜索筛选界面

滚动到指定位置(带偏移)

点击左上角的骰子图标,可以随机颜色(滚动到某一位置),通常我们使用recyclerView.scrollToPosition(int position)就可以实现。但是这个方法,会滚动到item的最边缘(红线位置),但是我希望他能够保留一定边距(绿色框框),看起来界面会和谐一点

image.png

解决办法如下:

private fun RecyclerView.scrollToPositionWithOffset(position: Int, offset: Int) {
    (layoutManager as GridLayoutManager)
        .scrollToPositionWithOffset(position, offset)
}

// 调用
binding.recyclerView.scrollToPositionWithOffset(
    Random.nextInt(0, adapter.itemCount - 1),
    16.dp(this)
)

用了kotlin扩展方法方便调用,这里的layoutManager根据实际情况来,我这里用列表到的是GridLayoutManager

小组件(App Widget)

提供了两种布局,小尺寸只显示颜色名称,大尺寸显示拼音和名称,效果如下:

Android小组件.jpg

iOS小组件.png

可能在部分系小尺寸统显示有问题,懒得搞了,这个组件大小搞的我脑壳疼,也没看到过什么好的解决方案,以下是我的配置:

// 31以下
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="57dp"
    android:minHeight="51dp"
    android:updatePeriodMillis="0"
    android:previewImage="@drawable/appwidget_preview"
    android:initialLayout="@layout/layout_wide_widget"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

// 31及以上
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:targetCellWidth="4"
    android:targetCellHeight="2"
    android:minResizeWidth="57dp"
    android:minResizeHeight="51dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp"
    android:updatePeriodMillis="0"
    android:previewImage="@drawable/appwidget_preview"
    android:initialLayout="@layout/layout_wide_widget"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable">
</appwidget-provider>

因为布局比较简单,所以尺寸兼容效果相对好一点

主动刷新小组件

当我们app没有运行的时候,添加小组件是没有数据的,当我们打开app的时候,通知小组件更新

// 刷新 Widget
sendBroadcast(Intent(this, ColorWidgetProvider::class.java).apply {
    action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
    val ids = AppWidgetManager.getInstance(application)
        .getAppWidgetIds(ComponentName(application, ColorWidgetProvider::class.java))
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
})

周期更新小组件

可通过配置updatePeriodMillis来设置时间,但是容易失效,所以使用WorkManager来通知更新,虽然WorkManager保证了周期执行,但如果app不在后台的话还是无法更新的,因为发送了广播app收不到,可能再加个服务就可以了,不加不加了

遗留的小问题

MIUI无法添加小组件

这段代码在MIUI上不生效,无法弹出添加小组件的弹窗

AppWidgetManager.getInstance(this).requestPinAppWidget(xxx)

如果添加该权限并授权,可以成功添加,但是无任何弹窗提示

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

当然最奇怪的还是我居然在MIUI的安卓小部件里找不到我自己的组件,我在原生都能看得到我的小组件的,也不知道是不是还需要配置什么,再一次头大

总结

这个app断断续续也写了好几个月,也也没啥功能还写了这么久。之前还看了下swiftUI,写了个iOS版本的,给我的感觉就是上手简单,写起来效率快多了,
其实这篇文章早就可以发了,就为了等app上架,可真煎熬。
个人开发者上架应用真的是难于上青天,对于安卓平台,国内一些主流应用市场(华米OV)都不对个人开发者开放了,要求低点的比如酷安、应用宝个人是可以上传的,但是需要软著,这又是一个头疼的事,申请基本一个月起步,除非花几百块找别人,三五天下证;
PS:现在App需要备案了,除非你不联网,应用宝就可以上架,酷安也要强制备案
ios也让我很难受,可能是我自己的问题,我注册流程走到付款了,当时想着先写完app再注册好了,就没付款,后来再去注册就提示账户存在问题,邮件联系后告诉我:

您的账号由于一个或多个原因,您无法完成 Apple Developer Program 的注册。

我想问清楚具体是什么原因,客服告知由系统判定,他们无法知道也无法干预,然后我寻思罢了,我再注册一个,还是失败,这次提示:

您使用另一 Apple ID 通过 Apple Developer App 验证了身份。要继续,请使用之前用于验证您身份的 Apple ID。

问号.jpeg

然后我又去把原来的账号注销掉,依旧无法注册成功...,最后无奈使用别人的信息注册了一个乛 з乛
所以,想注册苹果开发者的,注意最好是在同一个设备上一次性完成注册。