首页 > 技术知识 > 正文

我是一名移动开发人员,Kotlin出现后我立即切换到它的原因之一是它支持扩展。扩展允许我们向任何现有类添加方法,甚至向任何或可选类型(例如,Int?)添加方法。 如果我们在开发过程中扩展基类,所有派生类都会自动获得此扩展。我们还可以覆盖来自扩展的方法,这使得该机制更加灵活和强大。 这篇文章主要介绍几个非常有用的扩展函数,来让我们的代码更加的简洁和干净。 ‘Int.toDate()’ and ‘Int.asDate’ 我们在开发过程中经常会使用时间戳timestamp并表示日前或者时分,它的单位是秒或者毫秒。同时,我们我们也常常有需求将时间戳转化为日前,如果通过扩展函数可以简单是实现 有两种方式可以实现:通过扩展方法或者扩展只读属性,这两个方式都可以实现,没有差异,用哪一种只是个人喜好问题。

import java.util.Date fun Int.toDate(): Date = Date(this.toLong() * 1000L) val Int.asDate: Date get() = Date(this.toLong() * 1000L)

用法

val json = JSONObject(); json.put(“date”, 1598435781) val date = json.getIntOrNull(“date”)?.asDate

注意:在这个例子中,我使用了另一个扩展- getIntOrNull。如果JSON中存在,则返回int值,否则返回null,我相信大家可以很容易实现这个功能。

‘String.toDate(…)’ and ‘Date.toString(…)’ 日期对象的另一种流行转换是日前和字符串相互转换。我不是在谈论标准的Java/Kotlin toString()方法。在本例中,我们需要指定一种格式。

import java.text.ParseException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale fun String.toDate(format: String): Date? { val dateFormatter = SimpleDateFormat(format, Locale.US) return try { dateFormatter.parse(this) } catch (e: ParseException) { null } } fun Date.toString(format: String): String { val dateFormatter = SimpleDateFormat(format, Locale.US) return dateFormatter.format(this) }

注意:在这个例子中,我们每次都创建一个SimpleDateFormat对象。如果我们在列表中使用此扩展,请考虑从扩展代码中删除val dateFormatter = SimpleDateFormat(format, Locale.US),我们应该在外部创建一个SimpleDateFormat对象并将其作为全局变量或静态类成员。 用法

val format = “yyyy-MM-dd HH:mm:ss” val date = Date() val str = date.toString(format) val date2 = str.toDate(format)

‘String.toLocation(…)’ 当我们使用API并获得对象的坐标时,我们可以将它们作为两个不同的字段获得。但有时它是一个字段,用逗号分隔纬度和经度。 有了这个扩展,我们可以将一个字段的转换成两个字段的位置信息:

import android.location.Location fun String.toLocation(provider: String): Location? { val components = this.split(“,”) if (components.size != 2) return null val lat = components[0].toDoubleOrNull() ?: return null val lng = components[1].toDoubleOrNull() ?: return null val location = Location(provider); location.latitude = lat location.longitude = lng return location }

用法

val apiLoc = “41.6168, 41.6367”.toLocation(“API”)

‘String.containsOnlyDigits’ and ‘String.isAlphanumeric’ 我们来讨论一下字符串的属性。它们可以是空的,也可以不是——例如,它们可以只包含数字或字母数字字符。上面这些扩展可以帮助我们通过一行代码实现这个功能:

val String.containsDigit: Boolean get() = matches(Regex(“.*[0-9].*”)) val String.isAlphanumeric: Boolean get() = matches(Regex(“[a-zA-Z0-9]*”))

用法

val digitsOnly = “12345”.containsDigitOnly val notDigitsOnly = “abc12345”.containsDigitOnly val alphaNumeric = “abc123”.isAlphanumeric val notAlphanumeric = “ab.2a#1”.isAlphanumeric

‘Context.versionNumber’ and ‘Context.versionCode’ 在Android app中,一个i 叫好的用户体验是在关于或支持反馈页面上需要显示版本号。它将帮助用户了解他们是否需要更新,并在报告错误时提供有价值的信息。标准的Android功能需要几行代码和异常处理才能实现这个功能。这个扩展将允许我们在调用处通过一行代码实现:

import android.content.Context import android.content.pm.PackageManager val Context.versionName: String? get() = try { val pInfo = packageManager.getPackageInfo(packageName, 0); pInfo?.versionName } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() null } val Context.versionCode: Long? get() = try { val pInfo = packageManager.getPackageInfo(packageName, 0) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { pInfo?.longVersionCode } else { @Suppress(“DEPRECATION”) pInfo?.versionCode?.toLong() } } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() null }
<

用法

val vn = versionName ?: “Unknown” val vc = versionCode?.toString() ?: “Unknown” val appVersion = “App Version: $vn ($vc)”

‘Context.screenSize’ 另一个对Android有用的扩展是上下文扩展,它允许你获得设备屏幕大小。这个扩展返回屏幕尺寸的像素

import android.content.Context import android.graphics.Point import android.view.WindowManager @Suppress(“DEPRECATION”) val Context.screenSize: Point get() { val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager val display = wm.defaultDisplay val size = Point() display.getSize(size) return size }

用法

Log.d(TAG, “Users screen size: ${screenSize.x}x${screenSize.y}”)

‘Any.deviceName’ 为Any添加扩展函数需要仔细思考它的必要性。如果这样做,函数就会显示为全局的,所以基本上,这与声明一个全局函数是一样的。 在我们的下一个扩展,我们将获得一个Android设备的名称。因为它不需要任何上下文,我们将它作为任何扩展:

import android.os.Build import java.util.Locale val Any.deviceName: String get() { val manufacturer = Build.MANUFACTURER val model = Build.MODEL return if (model.startsWith(manufacturer)) model.capitalize(Locale.getDefault()) else manufacturer.capitalize(Locale.getDefault()) + ” ” + model }

用法

Log.d(TAG, “Users device: $deviceName”)

‘T.weak’ 这个case有点复杂但也是非常常见的case。假设我们有一个Activity和一个包含许多item的RecycleView。每个item都需要回调。假设它有一个委托接口,为实现这个功能我们需要将Activity本身传递给每个item,实现如下:

interface CellDelegate { fun buttonAClicked() fun buttonBClicked() } class Cell(context: Context?) : View(context) { // … var delegate: CellDelegate? = null fun prepare(arg1: String, arg2: Int, delegate: CellDelegate) { this.delegate = delegate } } class Act: Activity(), CellDelegate { // … fun createCell(): Cell { val cell = Cell(this) cell.prepare(“Milk”, 10, this) return cell } override fun buttonAClicked() { TODO(“Not yet implemented”) } override fun buttonBClicked() { TODO(“Not yet implemented”) } // … }
<

上面代码只是在展示它的结构。实际应用中,我们可以使用ViewHolder。 但是上面代码存在一个问题之一是Act和Cell相互引用,这可能导致内存泄漏。一个好的解决方案是使用WeakReference。包装在WeakReference中的委托变量不会影响我们的Act的引用计数器,所以只要我们关闭屏幕,它就会连同所有分配的item一起被销毁(或添加到队列中稍后销毁)。 我们通过简单的扩展允许得到任何对象来获得弱引用:

val <T> T.weak: WeakReference<T> get() = WeakReference(this)

用法

class Cell(context: Context?) : View(context) { // … private var delegate: WeakReference<CellDelegate>? = null fun prepare(arg1: String, arg2: Int, delegate: CellDelegate) { this.delegate = delegate.weak } fun callA() { delegate?.get()?.buttonAClicked() } fun callB() { delegate?.get()?.buttonBClicked() } }

我想强调这个扩展是通用的,它将与任何类型的兼容。 ‘Context.directionsTo(…)’ 从Android应用程序打开导航是一个很受欢迎的功能。Android是谷歌产品,和谷歌Maps一样。大多数安卓手机和平板电脑都预装了谷歌地图应用程序。最简单的解决方案是在Android地图应用程序中打开导航。如果没有安装导航,只需在网络浏览器中打开它。

import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.location.Location import android.net.Uri import java.util.Locale fun Context.directionsTo(location: Location) { val lat = location.latitude val lng = location.longitude val uri = String.format(Locale.US, “http://maps.google.com/maps?daddr=%f,%f”, lat, lng) try { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) intent.setClassName(“com.google.android.apps.maps”, “com.google.android.maps.MapsActivity”) startActivity(intent) } catch (e: ActivityNotFoundException) { e.printStackTrace() val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) startActivity(intent) } }
<

这是context的扩展,从编程的角度来看,这没问题。但从逻辑上讲,我会让它更具体。它可以是Activity的扩展或AppCompatActivity的扩展,这样可以避免Context在其他组建中使用,比如被Service使用。我们可以将可扩展类更改为我们在应用程序中使用的任何类。

‘AppCompatActivity.callTo(…)’ or ‘Activity.callTo(…)’ 我们使用与前一个扩展相同的逻辑,同样可以定义类似权限请求的扩展方法,这个扩展方法可以在所有的Activity中通用。

import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat fun AppCompatActivity.callTo(phoneNumber: String, requestCode: Int) { val intent = Intent(Intent.ACTION_CALL) intent.data = Uri.parse(“tel:$phoneNumber”) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { val permissions = arrayOfNulls<String>(1) permissions[0] = Manifest.permission.CALL_PHONE requestPermissions(permissions, requestCode) } else { startActivity(intent) } } else { startActivity(intent) } }
<

用法(在Activity中使用)

private val phone: String = “+1234567890” private fun call() { callTo(phone, callPermissionRequestCode) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (requestCode == callPermissionRequestCode) { if (permissions.isNotEmpty() && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { call() } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults) } } companion object { const val callPermissionRequestCode = 2001 }

‘String.asUri’ 我们通常认为互联网地址是一个字符串。我们可以把它输入引号。例如,“https://www.ebaina.com/articles/140000005260” 但是对于内部使用,Android有一种特殊类型:Uri。把一种转换成另一种很容易。下面的扩展允许我们将aString转换为具有验证的anUri。如果它不是一个有效的Uri,它将会返回null:

import android.net.Uri import android.webkit.URLUtil import java.lang.Exception val String.asUri: Uri? get() = try { if (URLUtil.isValidUrl(this)) Uri.parse(this) else null } catch (e: Exception) { null }

用法

val uri = “invalid_uri”.asUri val uri2 = “https://medium.com/@alex_nekrasov”.asUri

‘Uri.open(…)’, ‘Uri.openInside(…)’, and ‘Uri.openOutside(…)’ 现在,当我们有一个Uri时,我们可能希望在浏览器中打开它。有两种方法可以做到这一点: 1)app内打开 2)通过手机浏览器打开 我们通常想把用户留在app中,但有些schemas不能在app中打开。比如我们只想在app中打开http:// 或者是https:// 的链接 针对上面描述的上下文,我们可以添加三种不同的扩展函数。一个将在app内部打开Uri,另一个将打开外部浏览器,最后一个将根据schemas动态决定。 要在app中打开网页,我们需要创建一个单独的活动,或者使用一个库来为我们做这件事。为了简单起见,我选择了第二种方法用FinestWebView库。 我们可以在Gradle中注入如下:

dependencies { implementation com.thefinestartist:finestwebview:1.2.7 }

在manifest中修改如下:

<uses-permission android:name=”android.permission.INTERNET” /> <activity android:name=”com.thefinestartist.finestwebview.FinestWebViewActivity” android:configChanges=”keyboardHidden|orientation|screenSize” android:screenOrientation=”sensor” android:theme=”@style/FinestWebViewTheme.Light” />

扩展函数如下:

import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.net.Uri import com.thefinestartist.finestwebview.FinestWebView fun Uri.open(context: Context): Boolean = if (this.scheme == “http” || this.scheme == “https”) { openInside(context) true } else openOutside(context) fun Uri.openInside(context: Context) = FinestWebView.Builder(context).show(this.toString()) fun Uri.openOutside(context: Context): Boolean = try { val browserIntent = Intent(Intent.ACTION_VIEW, this) context.startActivity(browserIntent) true } catch (e: ActivityNotFoundException) { e.printStackTrace() false }
<

用法

val uri2 = “https://medium.com/@alex_nekrasov”.asUri uri2?.open(this)

‘Context.vibrate(…)’ 325/5000 有时候我们需要手机的一些物理反馈。例如,当用户点击某个按钮时,设备会震动。我将把讨论放在一边,不管它是否是一个好的实践,或者它是否超出了范围,最好还是专注于功能。 首先,将此权限添加到我们的manifest中:

<uses-permission android:name=”android.permission.VIBRATE” />

扩展函数

import android.content.Context import android.os.Build import android.os.VibrationEffect import android.os.Vibrator fun Context.vibrate(duration: Long) { val vib = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { vib.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE)) } else { @Suppress(“DEPRECATION”) vib.vibrate(duration) } }

用法

vibrate(500) // 500 ms // Should be called from Activity or other Context

或者

context.vibrate(500) // 500 ms // Can be called from any place having context variable

结论 我希望这些扩展对大家有用,并使大家的代码更简介、更干净。大家可以修改它们以满足大家的需求,并将它们包含在大家的项目中。不要忘记添加所有必要的权限到manifest中。

猜你喜欢