Android開發(fā)之如何自定義數(shù)字鍵盤詳解
前言
這篇文章是介紹Android中自定義鍵盤的一些套路,通過定義一個數(shù)字鍵盤為例,本篇的文章語言是基于Kotlin實現(xiàn)的,如果還沒有用或者不熟悉該語言的同學,可以自己補習,我之前也寫過入門文章。

效果圖
github:源碼傳送門
本地下載:源碼傳送門
加載鍵盤存儲鍵屬性的XML描述
我們下面的介紹都是依靠上圖的實現(xiàn)來展開的,首先是軟鍵盤的布局,我們需要我們的res/xml目錄下創(chuàng)建一個xml文件,根節(jié)點就是Keyboard,然后就是鍵盤的每一行Row,每一行中可以指定每一列,也就是具體的鍵Key,代碼實現(xiàn)
<?xml version="1.0" encoding="utf-8"?><!-- isRepeatable:長按時是否重復這個操作 --> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:horizontalGap="1px" android:keyHeight="7%p" android:keyWidth="33.33%p" android:verticalGap="1px"> <Row android:keyHeight="6%p"> <Key android:codes="-4" android:keyIcon="@drawable/hidden" android:keyWidth="100%" /> </Row> <Row> <Key android:codes="49" android:keyLabel="1" /> <Key android:codes="50" android:keyLabel="2" /> <Key android:codes="51" android:keyLabel="3" /> </Row> <Row> <Key android:codes="52" android:keyLabel="4" /> <Key android:codes="53" android:keyLabel="5" /> <Key android:codes="54" android:keyLabel="6" /> </Row> <Row> <Key android:codes="55" android:keyLabel="7" /> <Key android:codes="56" android:keyLabel="8" /> <Key android:codes="57" android:keyLabel="9" /> </Row> <Row> <Key android:codes="46" android:keyLabel="." /> <Key android:codes="48" android:keyLabel="0" /> <Key android:codes="-5" android:isRepeatable="true" android:keyIcon="@drawable/delete" /> </Row> </Keyboard>
在Keyboard節(jié)點屬性中,我們通過horizontalGap設(shè)置水平的間距,通過verticalGap設(shè)置垂直的間距,通過keyWidth設(shè)置每一個key的寬度,通過keyHeight設(shè)置。需要注意的地點是如果Keyboard ,Row和Key都可以指定寬高。通常我們可以指定在Keyboard 中設(shè)置每一個鍵的寬高就可以了。當然如果對特定行的寬高要有所調(diào)整,可以在Row 或者key上設(shè)置,例如我們示例圖中展示的最上面的一行,它的寬度比其它行都低了一點,則我們在第一行設(shè)置了屬性android:keyHeight="6%p"
在每一個key中有下面常用屬性
1、android:codes 官網(wǎng)介紹是說這個是該鍵的unicode 值或者逗號分隔值,當然我們也可以設(shè)置成我們想要的值,在源碼中提供了幾個特定的值
//就不解釋了,通過名字應(yīng)該看得出來 public static final int KEYCODE_SHIFT = -1; public static final int KEYCODE_MODE_CHANGE = -2; public static final int KEYCODE_CANCEL = -3; public static final int KEYCODE_DONE = -4; public static final int KEYCODE_DELETE = -5; public static final int KEYCODE_ALT = -6;
2、android:keyOutputText 設(shè)置該值后,當點擊key時回調(diào)onText(text: CharSequence?)會執(zhí)行,參數(shù)就是我們設(shè)置的值。
3、android:keyIcon設(shè)置key上顯示的icon
4、android:keyLabel 鍵上顯示的值
5、android:isRepeatable 當長按時是否重復該鍵設(shè)置的操作,例如我們刪除鍵可以設(shè)置此屬性。
6、android:keyEdgeFlags 該屬性有兩個值,分別是left,right,用與指定顯示在最左還是最右,一般不用此屬性。默認從左到右排列。
還有其它屬性,不在介紹,可以自己去查閱api
自定義KeyboardView
該類是用來渲染虛擬鍵盤的類,類中有一個接口OnKeyboardActionListener能檢測按鍵和觸摸動作,我們要自定義虛擬鍵盤,只需要繼承該類并實現(xiàn)該監(jiān)聽接口即可,當然我這里并沒有實現(xiàn)接口,我單獨創(chuàng)建了一個工具類,用于將自定義鍵盤View和EditText關(guān)聯(lián),并設(shè)置接口監(jiān)聽,這些稍后介紹到再說,我們最主要關(guān)注的就是onDraw方法,它可以讓我們自定義鍵盤的繪制,隨心所欲的畫我們想要的東西。當然,我們也可以不做任何實現(xiàn),它默認的有一種繪制。
class CustomKeyboardView : KeyboardView {
private var mKeyBoard: Keyboard? = null
constructor(context: Context, attrs: AttributeSet) : this(context, attrs,0) {}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
//
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mKeyBoard = this.keyboard
var keys: MutableList<Keyboard.Key>? = null
if (mKeyBoard != null) {
keys = mKeyBoard!!.keys
}
if (keys != null) {
for (key in keys) {
//可以自定義自己的繪制(例如某個按鈕繪制背景圖片和文字,亦或者更改某個按鈕顏色等)
if (key.codes[0] == -111) {//過濾指定某個鍵自定義繪制
}
}
}
}
}
在上面的onDraw方法中,我們通過this.keyboard(即java的getKeyboard方法,是KeyboardView 中的方法)獲取Keyboard對象,并通過mKeyBoard!!.keys獲取鍵盤的Key對象,即每一個鍵對象,如果我們想自定義繪制,就可以自己實現(xiàn)繪制,當然也可以針對個人鍵繪制,例如鍵上字體顏色,背景等。例如我們針對Key的code是 -111的自定義一些繪制操作。
if (key.codes[0] == -111) {//過濾指定某個鍵自定義繪制
//繪制后,原來xml中的keyLabel以及keyIcon會被覆蓋,如需顯示文字
//需要自己重新繪制,要后繪制文字,否則文字不顯示
drawBackground(R.drawable.bg_keyboardview1, canvas, key)
drawTextOrIcon(canvas, key)
}
背景selector
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/btnpressed" android:state_pressed="true"/> <item android:drawable="@color/btnnormal"/> </selector>
需要注意的是需要先繪制背景,再繪制文字或icon,否則文字或者icon就看不到了,相信你肯定知道為啥,真不知道的話那....
//繪制背景
fun drawBackground(drawableId: Int, canvas: Canvas, key: Keyboard.Key) {
var drawable = resources.getDrawable(drawableId)
var drawableState: IntArray = key.currentDrawableState
if (key.codes[0] != 0) {
drawable.state=drawableState
}
drawable.bounds = Rect(key.x, key.y, key.x + key.width, key.height + key.y)
drawable.draw(canvas)
}
繪制背景前先通過key.currentDrawableState(java的getCurrentDrawableState() 方法,后面不在提了)獲取當前的狀態(tài),然后設(shè)置到drawable,然后通過Rect指定繪制的區(qū)域。Rect參數(shù)分別是左上右下。key.x,key.對應(yīng)的就是該key的左上角的坐標,則left=key.x, top=key.y, right=key.x+key.width, bottom=key.y+key.height然后調(diào)用 drawable.draw(canvas)開始繪制。
繪制完成背景之后,我們開始繪制文字或者icon。
//繪制文字或圖標
fun drawTextOrIcon(canvas: Canvas, key: Keyboard.Key) {
var bounds = Rect()
var paint = Paint()
paint.color = Color.WHITE
paint.isAntiAlias = true
paint.textAlign = Paint.Align.CENTER
paint.typeface = Typeface.DEFAULT
if (key.label != null) {
var label = key.label.toString()
//為了將字體大小與默認繪制的Label字體大小相同,需要反射獲取默認大小。然后在此處設(shè)置文字大小
//還有一種取巧的方法在布局文件keyboardview中設(shè)置keyTextSize,labelTextSize
var field = KeyboardView::class.java.getDeclaredField("mLabelTextSize")
field.isAccessible = true
var labelTextSize = field.get(this) as Int
paint.textSize = labelTextSize.toFloat()
paint.getTextBounds(label, 0, label.length, bounds)
canvas.drawText(label, (key.x + key.width / 2).toFloat(), (key.y + key.height / 2 + bounds.height() / 2).toFloat(), paint)
} else if (key.icon != null) {
key.icon.bounds = Rect(key.x + (key.width - key.icon.intrinsicWidth) / 2, key.y + (key.height - key.icon.intrinsicHeight) / 2, key.x + (key.width - key.icon.intrinsicWidth) / 2 + key.icon.intrinsicWidth, key.y + (key.height - key.icon.intrinsicHeight) / 2 + key.icon.intrinsicHeight)
key.icon.draw(canvas)
}
}
通過上面的代碼,我們做了下判斷如果有l(wèi)abel的時候就繪制文字,如果沒有但是有icon就繪制icon,否則不做處理。在這里可以指定繪制文字的大小,顏色等。需要注意的一點是文字大小,為了和顯示的其他默認繪制key的大小相同,需要獲取KeyboardView中的mLabelTextSize或者mKeyTextSize,因為該變量沒有提供暴露方法,所以需要我們反射操作。當然還有一種取巧的方法,我們可以在xml中指定字體大小,在此設(shè)置成相同大小。對于坐標區(qū)域的計算上面已經(jīng)做了分析。
布局使用
<?xml version="1.0" encoding="utf-8"?><!-- background:整個鍵盤的背景色 keyBackground :設(shè)置鍵的背景 keyPreviewHeight:預覽高度 keyPreviewLayout :設(shè)置預覽布局 keyPreviewOffset :設(shè)置反饋的垂直偏移量 keyTextColor :設(shè)置key標簽文字顏色 keyTextSize:設(shè)置key標簽字體大小 labelTextSize:設(shè)置帶文本和圖標的鍵上個的文本的小大 --> <com.code4android.keyboard.CustomKeyboardView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/keyboard_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/keyborad_line_color" android:focusable="true" android:focusableInTouchMode="true" android:keyBackground="@drawable/bg_keyboardview" android:keyPreviewHeight="35dp" android:keyPreviewLayout="@layout/keyboard_key_preview" android:keyPreviewOffset="0dp" android:keyTextColor="#8a8a8a" android:keyTextSize="18sp" android:labelTextSize="18sp" android:paddingTop="0dp" android:shadowColor="#fff" android:shadowRadius="0.0" />
我們創(chuàng)建了自定義的View之后,需要再創(chuàng)建上面layout供加載。keyBackground屬性是設(shè)置Key的背景,一般我們可以設(shè)置一個selected選擇器。keyPreviewHeight設(shè)置預覽的高度,即我們點擊時會有一個提示效果。keyPreviewLayout是我們預覽的布局,它需要是一個TextView 。keyPreviewOffset是預覽的偏移量,keyTextColor設(shè)置key字體顏色,shadowRadius我們一般設(shè)置為0,它表示字體的陰影,如果不設(shè)置0.看起來回模糊。
創(chuàng)建工具類
在工具類中創(chuàng)建了兩個構(gòu)造方法
constructor(activity: Activity) : this(activity, true, false)
/**
* @param activity
* @param isRandom 是否時隨機鍵盤
* @param mIsDecimal 是否支持小數(shù)輸入
*/
constructor(activity: Activity, isRandom: Boolean, isDecimal: Boolean) {
mActivity = activity
mIsRandom = isRandom
mIsDecimal = isDecimal
mKeyboard = Keyboard(mActivity, R.xml.keyboard)
addViewToRoot()
}
//加載自定義的鍵盤layout
private fun addViewToRoot() {
mKeyBoardViewContainer = mActivity.layoutInflater.inflate(R.layout.keyboardview, null)
//var frameLayout: FrameLayout = mActivity.window.decorView as FrameLayout//不要直接往DecorView(狀態(tài)欄,內(nèi)容,導航欄)中addView,如使用這個則最后顯示布局不全(一部分內(nèi)容在導航欄區(qū)域)
var frameLayout: FrameLayout = mActivity.window.decorView.find(android.R.id.content)
var lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
lp.gravity = Gravity.BOTTOM
frameLayout.addView(mKeyBoardViewContainer, lp)
mKeyBoardView = mKeyBoardViewContainer.find(R.id.keyboard_view)
}
在構(gòu)造方法中初始化Keyboard,以及布局文件,在代碼中我們看到我們獲取到DecorView中id為android.R.id.content的布局,該布局是FrameLayout 布局,我們創(chuàng)建的布局都是放在這個布局中了,對這方面不理解的可以看看我之前寫的文章深入分析setContentView。為了讓我們自定義的鍵盤顯示在最下面,設(shè)置Gravity為BOTTOM,然后通過frameLayout.addView(mKeyBoardViewContainer, lp)添加到FrameLayout 中。
除此之外,我們創(chuàng)建一個函數(shù)attachTo(EditText)將EditText與我們自定義的鍵盤綁定
fun attachTo(editText: EditText) {
//如果editText與上次設(shè)置的是同一個對象,并且鍵盤已經(jīng)正在在顯示,不再執(zhí)行后續(xù)操作
if (mEditText != null && mEditText == editText && mKeyBoardView.visibility == View.VISIBLE) return
mEditText = editText
Log.e(TAG, "attachTo")
//根據(jù)焦點及點擊監(jiān)聽,來顯示或者隱藏鍵盤
onFoucsChange()
//隱藏系統(tǒng)鍵盤
hideSystemSoftKeyboard()
//顯示自定義鍵盤
showSoftKeyboard()
}
private fun onFoucsChange() {
mEditText!!.setOnFocusChangeListener { v, hasFocus ->
Log.e(TAG, "onFoucsChange:$hasFocus" + v)
//如果獲取焦點,并且當前鍵盤沒有顯示,則顯示,并執(zhí)行動畫
if (hasFocus && mKeyBoardView.visibility != View.VISIBLE) {
mKeyBoardView.visibility = View.VISIBLE
startAnimation(true)
} else if (!hasFocus && mKeyBoardView.visibility == View.VISIBLE) {
//如果當前時失去較大,并且當前在鍵盤正在顯示,則隱藏
mKeyBoardView.visibility = View.GONE
startAnimation(false)
}
}
mEditText!!.setOnClickListener {
Log.e(TAG, "setOnClickListener")
//根據(jù)上面焦點的判斷,如果已經(jīng)獲取到焦點,并且鍵盤隱藏。再次點擊時,
// 焦點改變函數(shù)不會回調(diào),所以在此判斷如果隱藏就顯示
if (mKeyBoardView.visibility == View.GONE) {
mKeyBoardView.visibility = View.VISIBLE
startAnimation(true)
}
}
}
private fun hideSystemSoftKeyboard() {
//11版本開始需要反射setShowSoftInputOnFocus方法設(shè)置false,來隱藏系統(tǒng)軟鍵盤
if (Build.VERSION.SDK_INT > 10) {
var clazz = EditText::class.java
var setShowSoftInputOnFocus: Method? = null
setShowSoftInputOnFocus = clazz.getMethod("setShowSoftInputOnFocus", Boolean::class.java)
setShowSoftInputOnFocus.isAccessible = true
setShowSoftInputOnFocus.invoke(mEditText, false)
} else {
mEditText!!.inputType = InputType.TYPE_NULL
}
var inputMethodManager = mActivity.applicationContext.inputMethodManager
inputMethodManager.hideSoftInputFromWindow(mEditText!!.windowToken, 0)
}
private fun showSoftKeyboard() {
if (mIsRandom) {
//生成隨機鍵盤
generateRandomKey()
} else {
//有序鍵盤
mKeyBoardView.keyboard = mKeyboard
}
mKeyBoardView.isEnabled = true
//設(shè)置預覽,如果設(shè)置false,則就不現(xiàn)實預覽效果
mKeyBoardView.isPreviewEnabled = true
//設(shè)置可見
mKeyBoardView.visibility = View.VISIBLE
//指定鍵盤彈出動畫
startAnimation(true)
//設(shè)置監(jiān)聽
mKeyBoardView.setOnKeyboardActionListener(mOnKeyboardActionListener())
}
private fun generateRandomKey() {
var keys = mKeyboard.keys
var numberKeys = mutableListOf<Keyboard.Key>()
//保存數(shù)字
var nums = mutableListOf<Int>()
//0的ASCII碼是48,之后順序加1
for (key in keys) {
//過濾數(shù)字鍵盤
if (key.label != null && "0123456789".contains(key.label)) {
nums.add(Integer.parseInt(key.label.toString()))
numberKeys.add(key)
}
}
var random = Random()
var changeKey = 0//更改numberKeys對應(yīng)的數(shù)值
while (nums.size > 0) {
var size = nums.size
var randomNum = nums[random.nextInt(size)]
var key = numberKeys[changeKey++]
key.codes[0] = 48 + randomNum
key.label = randomNum.toString()
nums.remove(randomNum)
}
mKeyBoardView.keyboard = mKeyboard
}
具體的解釋已在代碼中體現(xiàn)。
設(shè)置鍵盤監(jiān)聽
在上面代碼中我們看一句mKeyBoardView.setOnKeyboardActionListener(mOnKeyboardActionListener()) ,它就是設(shè)置鍵盤的監(jiān)聽。OnKeyboardActionListener接口是KeyboardView的內(nèi)部類,我們在此設(shè)置監(jiān)聽可以指定在對應(yīng)的回調(diào)種操作EditText。該接口回調(diào)方法如下
1、swipeUp()
當用戶快速將手指從下向上移動時調(diào)用
2、swipeDown 方法
當用戶快速將手指從上向下移動時調(diào)用
3、swipeLeft
當用戶快速將手指從右向左移動時調(diào)用
4、swipeRight()
當用戶快速將手指從左向右移動時調(diào)用
5、onPress(primaryCode: Int)
點擊key時調(diào)用primaryCode時對應(yīng)key的codes值
6、onRelease(primaryCode: Int)
釋放key時調(diào)用
7、onKey(primaryCode: Int, keyCodes: IntArray?)
我選擇在此對EditText的編輯,onPress之后調(diào)用的。
8、onText(text: CharSequence?)
設(shè)置keyOutputText時會會回調(diào)
具體實現(xiàn)
inner class mOnKeyboardActionListener : KeyboardView.OnKeyboardActionListener {
override fun swipeRight() {
Log.e(TAG, "swipeRight")
}
override fun onPress(primaryCode: Int) {
Log.e(TAG, "onPress")
//添加震動效果
mActivity.applicationContext.vibrator.vibrate(50)
////指定隱藏(確定)刪除不顯示預覽
mKeyBoardView.isPreviewEnabled = !(primaryCode == Keyboard.KEYCODE_DONE || primaryCode == Keyboard.KEYCODE_DELETE)
}
override fun onRelease(primaryCode: Int) {
Log.e(TAG, "onRelease")
}
override fun swipeLeft() {
Log.e(TAG, "swipeLeft")
}
override fun swipeUp() {
Log.e(TAG, "swipeUp")
}
override fun swipeDown() {
Log.e(TAG, "swipeDown")
}
override fun onKey(primaryCode: Int, keyCodes: IntArray?) {
Log.e(TAG, "onKey primaryCode:$primaryCode keyCodes:$keyCodes")
if (mEditText == null) throw RuntimeException("The mEditText is null,Please call attachTo method")
mEditText?.let {
var editable: Editable = it.text
var textString = editable.toString()
//獲取光標位置
var start = it.selectionStart
when (primaryCode) {
//如果是刪除鍵,editable有值并且光標大于0(即光標之前有內(nèi)容),則刪除
Keyboard.KEYCODE_DELETE -> {
if (!editable.isNullOrEmpty()) {
if (start > 0) {
editable.delete(start - 1, start)
} else {
}
} else {
}
}
Keyboard.KEYCODE_DONE -> {
hideSoftKeyboard()
mOnOkClick?.let {
//點擊確定時,寫一個回調(diào),如果你對有確定的需求
it.onOkClick()
}
}
else -> {
// 由于promaryCode是用的ASCII碼,則直接轉(zhuǎn)換字符即可,46是小數(shù)點
if (primaryCode != 46 ) {
//如果點擊的是數(shù)字,不是小數(shù)點,則直接寫入EditText,由于我codes使用的是ASCII碼,
// 則可以直接轉(zhuǎn)換為數(shù)字。當然可以你也可以獲取label,或者根據(jù)你自己隨便約定。
editable.insert(start, Character.toString(primaryCode.toChar()))
} else {
//如果點擊的是逗號
if (mIsDecimal && primaryCode == 46) {
if ("" == textString) {
//如果點的是小數(shù)點,并且當前無內(nèi)容,自動加0
editable.insert(start, "0.")
} else if (!textString.contains(".")) {
//當前內(nèi)容不含有小數(shù)點,并且光標在第一個位置,依然加0操作
if (start == 0) {
editable.insert(start, "0.")
} else {
editable.insert(start, ".")
}
} else {
//如果是不允許小數(shù)輸入,或者允許小數(shù),但是已經(jīng)有小數(shù)點,則不操作
}
} else {
}
}
}
}
}
}
override fun onText(text: CharSequence?) {
Log.e(TAG, "onText:" + text.toString())
}
}
fun hideSoftKeyboard(): Boolean {
if (mEditText == null) return false
var visibility = mKeyBoardView.visibility
if (visibility == View.VISIBLE) {
startAnimation(false)
mKeyBoardView.visibility = View.GONE
return true
}
return false
}
fun startAnimation(isIn: Boolean) {
Log.e(TAG, "startAnimation")
var anim: Animation
if (isIn) {
anim = AnimationUtils.loadAnimation(mActivity, R.anim.anim_bottom_in)
} else {
anim = AnimationUtils.loadAnimation(mActivity, R.anim.anim_bottom_out)
}
mKeyBoardViewContainer.startAnimation(anim)
}
當點擊的是KEYCODE_DONE 時,調(diào)用hideSoftKeyboard函數(shù)隱藏鍵盤,并執(zhí)行隱藏動畫,動畫的xml文件就不在貼出了。
具體使用方式如下
keyboardUtli = KeyBoardUtil(this@KeyBoardDemoActivity)
et_keyboard.setOnTouchListener { v, event ->
keyboardUtli?.attachTo(et_keyboard)
//設(shè)置是否可以輸入小數(shù)
keyboardUtli?.mIsDecimal = true
false
}
et_keyboard2.setOnTouchListener { v, event ->
keyboardUtli?.attachTo(et_keyboard2)
keyboardUtli?.mIsDecimal = false
false
}
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Android獲取網(wǎng)絡(luò)連接狀態(tài)新方法整理
這篇文章主要給大家介紹了關(guān)于Android獲取網(wǎng)絡(luò)連接狀態(tài)新方法的相關(guān)資料,在開發(fā)安卓移動端時幾乎每一個app都需要連接網(wǎng)絡(luò),因此對設(shè)備的網(wǎng)絡(luò)狀態(tài)檢測是很有必要的,需要的朋友可以參考下2023-11-11
Android如何自定義View實現(xiàn)橫向的雙水波紋進度條
最近有個需求需要實現(xiàn)自定義加載進度條,于是深入研究了一下,這篇文章主要給大家介紹了關(guān)于Android如何自定義View實現(xiàn)橫向的雙水波紋進度條的相關(guān)資料,需要的朋友可以參考下2021-11-11
Android實現(xiàn)跟隨手指拖動并自動貼邊的View樣式(實例demo)
本文通過實例代碼給大家介紹了android實現(xiàn)跟隨手指拖動并自動貼邊的View樣式,效果非常棒,具有參考借鑒價值,需要的朋友參考下吧2017-01-01
Android Studio多渠道打包、自定義打包APK名稱
Android Studio為我們提供了簡便的方法,可以多渠道打包,一次打包所有的渠道包。這篇文章主要介紹了Android Studio多渠道打包、自定義打包APK名稱,需要的朋友可以參考下2018-01-01
Android studio利用gradle打jar包并混淆的方法詳解
昨天準備把寫好的代碼使用gradle打jar包出來,并打算加混淆。打jar包容易,結(jié)果在混淆上走了彎路。所以這篇文章主要介紹了關(guān)于Android studio利用gradle打jar包并混淆的方法,需要的朋友可以參考下。2017-03-03

