From 2c12943dafe18b2e7c556bda6b0e6dc97efc8477 Mon Sep 17 00:00:00 2001 From: lijia Date: Sun, 3 Apr 2022 18:17:37 +0800 Subject: [PATCH] 1.1.0 --- .idea/gradle.xml | 1 + app/build.gradle | 1 + app/src/main/assets/province.json | 5 + .../java/com/dahe/pass/bean/BusInfoBean.java | 9 + .../java/com/dahe/pass/bean/PassBean.java | 9 + .../account/activity/BusinessInfoActivity.kt | 196 ++- .../pass/ui/gone/activity/AddGoActivity.kt | 34 +- .../pass/ui/gone/activity/EditGoActivity.kt | 1 + .../pass/ui/home/activity/ApplyActivity.kt | 30 +- app/src/main/res/drawable-xxhdpi/nsr.png | Bin 0 -> 2020 bytes app/src/main/res/layout/activity_add_go.xml | 1057 ++++++------ app/src/main/res/layout/activity_apply.xml | 5 + .../res/layout/activity_business_info.xml | 62 +- app/src/main/res/layout/activity_edit_go.xml | 43 + app/src/main/res/layout/fragment_gone.xml | 1 + app/src/main/res/layout/fragment_home.xml | 1 + app/src/main/res/layout/item_apply.xml | 1 + app/src/main/res/layout/item_gone.xml | 1 + keybordlib/.gitignore | 1 + keybordlib/bintray.gradle | 12 + keybordlib/build.gradle | 42 + keybordlib/consumer-rules.pro | 0 keybordlib/gradle.properties | 3 + keybordlib/proguard-rules.pro | 21 + .../king/keyboard/ExampleInstrumentedTest.kt | 24 + keybordlib/src/main/AndroidManifest.xml | 5 + .../main/java/com/king/keyboard/Keyboard.java | 913 ++++++++++ .../java/com/king/keyboard/KeyboardView.java | 1505 +++++++++++++++++ .../java/com/king/keyboard/KingKeyboard.kt | 1141 +++++++++++++ .../com/king/keyboard/KingKeyboardUtil.kt | 55 + .../com/king/keyboard/KingKeyboardView.kt | 316 ++++ .../king_keyboard_key_all_caps.png | Bin 0 -> 1673 bytes .../king_keyboard_key_cancel.png | Bin 0 -> 1390 bytes .../drawable-xxhdpi/king_keyboard_key_cap.png | Bin 0 -> 1524 bytes .../king_keyboard_key_delete.png | Bin 0 -> 2096 bytes .../king_keyboard_key_license_plate.png | Bin 0 -> 2152 bytes ...king_keyboard_key_license_plate_number.png | Bin 0 -> 1787 bytes .../king_keyboard_key_lower.png | Bin 0 -> 1819 bytes .../king_keyboard_key_space.png | Bin 0 -> 556 bytes .../king_keyboard_view_bg.9.png | Bin 0 -> 979 bytes .../drawable/king_keyboard_done_key_bg.xml | 17 + .../res/drawable/king_keyboard_key_bg.xml | 17 + .../drawable/king_keyboard_none_key_bg.xml | 17 + .../drawable/king_keyboard_special_key_bg.xml | 17 + .../res/layout/king_keyboard_container.xml | 20 + keybordlib/src/main/res/values-zh/strings.xml | 6 + keybordlib/src/main/res/values/attrs.xml | 106 ++ keybordlib/src/main/res/values/colors.xml | 26 + keybordlib/src/main/res/values/dimens.xml | 22 + keybordlib/src/main/res/values/strings.xml | 5 + .../main/res/xml/king_keyboard_id_card.xml | 71 + .../src/main/res/xml/king_keyboard_letter.xml | 125 ++ .../res/xml/king_keyboard_letter_number.xml | 183 ++ .../res/xml/king_keyboard_license_plate.xml | 163 ++ .../xml/king_keyboard_license_plate_more.xml | 163 ++ .../king_keyboard_license_plate_number.xml | 154 ++ .../king_keyboard_license_plate_province.xml | 154 ++ .../king_keyboard_lowercase_letter_only.xml | 118 ++ .../src/main/res/xml/king_keyboard_normal.xml | 141 ++ .../xml/king_keyboard_normal_mode_change.xml | 147 ++ .../xml/king_keyboard_normal_more_symbol.xml | 147 ++ .../src/main/res/xml/king_keyboard_number.xml | 71 + .../res/xml/king_keyboard_number_decimal.xml | 71 + .../src/main/res/xml/king_keyboard_phone.xml | 71 + .../king_keyboard_uppercase_letter_only.xml | 118 ++ .../java/com/king/keyboard/ExampleUnitTest.kt | 18 + settings.gradle | 1 + 67 files changed, 7127 insertions(+), 537 deletions(-) create mode 100644 app/src/main/res/drawable-xxhdpi/nsr.png create mode 100644 keybordlib/.gitignore create mode 100644 keybordlib/bintray.gradle create mode 100644 keybordlib/build.gradle create mode 100644 keybordlib/consumer-rules.pro create mode 100644 keybordlib/gradle.properties create mode 100644 keybordlib/proguard-rules.pro create mode 100644 keybordlib/src/androidTest/java/com/king/keyboard/ExampleInstrumentedTest.kt create mode 100644 keybordlib/src/main/AndroidManifest.xml create mode 100644 keybordlib/src/main/java/com/king/keyboard/Keyboard.java create mode 100644 keybordlib/src/main/java/com/king/keyboard/KeyboardView.java create mode 100644 keybordlib/src/main/java/com/king/keyboard/KingKeyboard.kt create mode 100644 keybordlib/src/main/java/com/king/keyboard/KingKeyboardUtil.kt create mode 100644 keybordlib/src/main/java/com/king/keyboard/KingKeyboardView.kt create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_all_caps.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_cancel.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_cap.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_delete.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_license_plate.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_license_plate_number.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_lower.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_space.png create mode 100644 keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_view_bg.9.png create mode 100644 keybordlib/src/main/res/drawable/king_keyboard_done_key_bg.xml create mode 100644 keybordlib/src/main/res/drawable/king_keyboard_key_bg.xml create mode 100644 keybordlib/src/main/res/drawable/king_keyboard_none_key_bg.xml create mode 100644 keybordlib/src/main/res/drawable/king_keyboard_special_key_bg.xml create mode 100644 keybordlib/src/main/res/layout/king_keyboard_container.xml create mode 100644 keybordlib/src/main/res/values-zh/strings.xml create mode 100644 keybordlib/src/main/res/values/attrs.xml create mode 100644 keybordlib/src/main/res/values/colors.xml create mode 100644 keybordlib/src/main/res/values/dimens.xml create mode 100644 keybordlib/src/main/res/values/strings.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_id_card.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_letter.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_letter_number.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_license_plate.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_license_plate_more.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_license_plate_number.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_license_plate_province.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_lowercase_letter_only.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_normal.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_normal_mode_change.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_normal_more_symbol.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_number.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_number_decimal.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_phone.xml create mode 100644 keybordlib/src/main/res/xml/king_keyboard_uppercase_letter_only.xml create mode 100644 keybordlib/src/test/java/com/king/keyboard/ExampleUnitTest.kt diff --git a/.idea/gradle.xml b/.idea/gradle.xml index c69b70f..521bcdf 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -11,6 +11,7 @@ diff --git a/app/build.gradle b/app/build.gradle index df509ab..55210bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,4 +114,5 @@ dependencies { implementation project(path: ':mylibrary') + implementation project(path: ':keybordlib') } \ No newline at end of file diff --git a/app/src/main/assets/province.json b/app/src/main/assets/province.json index 2166cb4..05ddb15 100644 --- a/app/src/main/assets/province.json +++ b/app/src/main/assets/province.json @@ -2367,6 +2367,11 @@ "area": [ "中原区", "金水区", + "高新区", + "航空港区", + "国际物流园区", + "经开区", + "郑东新区", "二七区", "管城回族区", "上街区", diff --git a/app/src/main/java/com/dahe/pass/bean/BusInfoBean.java b/app/src/main/java/com/dahe/pass/bean/BusInfoBean.java index e8c0ff7..a5777f3 100644 --- a/app/src/main/java/com/dahe/pass/bean/BusInfoBean.java +++ b/app/src/main/java/com/dahe/pass/bean/BusInfoBean.java @@ -14,6 +14,7 @@ public class BusInfoBean implements Serializable { private String code; private String enterpriseName; private String pwd; + private String unifiedSocialCreditCode;//统一社会信用代码 private String nationalClassA;//国家A级 private String businessType;//业务类型 private String avatarUrl; @@ -25,6 +26,14 @@ public class BusInfoBean implements Serializable { private String name;//厂区地址 private ParamsDao params; + public String getUnifiedSocialCreditCode() { + return unifiedSocialCreditCode; + } + + public void setUnifiedSocialCreditCode(String unifiedSocialCreditCode) { + this.unifiedSocialCreditCode = unifiedSocialCreditCode; + } + public String getName() { return name; } diff --git a/app/src/main/java/com/dahe/pass/bean/PassBean.java b/app/src/main/java/com/dahe/pass/bean/PassBean.java index 963483a..4d2a714 100644 --- a/app/src/main/java/com/dahe/pass/bean/PassBean.java +++ b/app/src/main/java/com/dahe/pass/bean/PassBean.java @@ -22,6 +22,7 @@ public class PassBean implements Serializable { private String driverPhone; private String startPoint; private String endPoint; + private String driverIdcard; private String createTime; private String startTime; private String endTime; @@ -45,6 +46,14 @@ public class PassBean implements Serializable { private String notUsedNumMF; private String notUsedNumSF; + public String getDriverIdcard() { + return driverIdcard; + } + + public void setDriverIdcard(String driverIdcard) { + this.driverIdcard = driverIdcard; + } + public String getNotUsedNumMF() { return notUsedNumMF; } diff --git a/app/src/main/java/com/dahe/pass/ui/account/activity/BusinessInfoActivity.kt b/app/src/main/java/com/dahe/pass/ui/account/activity/BusinessInfoActivity.kt index 899e42a..3700d99 100644 --- a/app/src/main/java/com/dahe/pass/ui/account/activity/BusinessInfoActivity.kt +++ b/app/src/main/java/com/dahe/pass/ui/account/activity/BusinessInfoActivity.kt @@ -1,19 +1,37 @@ package com.dahe.pass.ui.account.activity +import android.annotation.SuppressLint +import android.graphics.Color import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.View +import android.widget.Toast +import com.bigkoo.pickerview.builder.OptionsPickerBuilder +import com.bigkoo.pickerview.view.OptionsPickerView import com.dahe.mylibrary.base.BaseActivity import com.dahe.mylibrary.net.CommonResponseBean import com.dahe.mylibrary.utils.ActivityUtils import com.dahe.pass.R import com.dahe.pass.base.AppConfig import com.dahe.pass.bean.BusInfoBean +import com.dahe.pass.bean.JsonBean import com.dahe.pass.net.BaseObserver import com.dahe.pass.net.DataManager import com.dahe.pass.net.RxHttpCallBack import com.dahe.pass.ui.account.LoginActivity import com.dahe.pass.utils.AppActivityUtils +import com.dahe.pass.utils.GetJsonDataUtil +import com.google.gson.Gson +import com.lxj.xpopup.XPopup +import com.lxj.xpopup.util.KeyboardUtils +import kotlinx.android.synthetic.main.activity_add_go.* +import kotlinx.android.synthetic.main.activity_apply.* +import kotlinx.android.synthetic.main.activity_apply.etPhone import kotlinx.android.synthetic.main.activity_business_info.* +import kotlinx.android.synthetic.main.activity_business_info.etQymc +import org.json.JSONArray +import java.util.ArrayList /** * @ClassName BusinessInfoActivity @@ -29,6 +47,8 @@ class BusinessInfoActivity : BaseActivity(), View.OnClickListener { btnGo.setOnClickListener(this) tv_yhxy.setOnClickListener(this) tv_yszc.setOnClickListener(this) + etDj.setOnClickListener(this) + etDz.setOnClickListener(this) } override fun initDate() { @@ -36,39 +56,63 @@ class BusinessInfoActivity : BaseActivity(), View.OnClickListener { override fun onClick(view: View) { super.onClick(view) - when(view.id){ - R.id.btnGo->go() + when (view.id) { + R.id.btnGo -> go() + R.id.etDz -> { + KeyboardUtils.hideSoftInput(etQymc) + if (isLoaded) { + setProText() + } else { + mHandler.sendEmptyMessage(MSG_LOAD_DATA) + } + } + R.id.tv_yhxy -> AppActivityUtils.openWebViewActivity(this, "用户服务协议", "https://downtest.dahehuodongbao.com/passcheck/user-agreement.html"); R.id.tv_yszc -> AppActivityUtils.openWebViewActivity(this, "隐私政策", "https://downtest.dahehuodongbao.com/passcheck/user-privacy.html") + R.id.etDj -> { + XPopup.Builder(this).hasShadowBg(false).offsetY(50).atView(etDj) + .popupWidth(500) + .asAttachList(arrayOf("2A", "3A", "4A", "5A", "其他"), + null, + { position, text -> etDj.text = text }).show() + } } } - fun go(){ + fun go() { val qymc = etQymc.text.toString() - val ywlx = etYwlx.text.toString() +// val ywlx = etYwlx.text.toString() val etDz = etDz.text.toString() val etTbr = etTbr.text.toString() - if (qymc.isNullOrEmpty()){ + if (qymc.isNullOrEmpty()) { showToast("请输入企业名称") return } - if (ywlx.isNullOrEmpty()){ - showToast("请输入业务类型") - return - } +// if (ywlx.isNullOrEmpty()) { +// showToast("请输入业务类型") +// return +// } - if (etDz.isNullOrEmpty()){ + if (etDz.isNullOrEmpty()) { showToast("请输入注册地址") return } + if (etNsr.text.toString().isNullOrEmpty() || etFzr.text.toString().isNullOrEmpty() || + etFzrPhone.text.toString().isNullOrEmpty() || etDj.text.toString().isNullOrEmpty() || + etGsdz.text.toString().isNullOrEmpty() + ) { + showToast("请完善信息") + return + } + val extras = intent.extras - val bean : BusInfoBean= extras?.getSerializable(AppConfig.BEAN) as BusInfoBean + val bean: BusInfoBean = extras?.getSerializable(AppConfig.BEAN) as BusInfoBean val busInfoBean = BusInfoBean() val paramsDao = BusInfoBean.ParamsDao() @@ -76,12 +120,13 @@ class BusinessInfoActivity : BaseActivity(), View.OnClickListener { busInfoBean.phone = bean.phone busInfoBean.params = paramsDao busInfoBean.pwd = bean.pwd + busInfoBean.unifiedSocialCreditCode = etNsr.text.toString() busInfoBean.enterpriseName = qymc busInfoBean.nationalClassA = etDj.text.toString() - busInfoBean.businessType = ywlx +// busInfoBean.businessType = ywlx busInfoBean.placeOfRegistration = etDz busInfoBean.companyAddress = etGsdz.text.toString() - busInfoBean.plantAddress = etCqdz.text.toString() +// busInfoBean.plantAddress = etCqdz.text.toString() busInfoBean.contacts = etFzr.text.toString() busInfoBean.contactsPhone = etFzrPhone.text.toString() busInfoBean.name = etTbr @@ -90,11 +135,134 @@ class BusinessInfoActivity : BaseActivity(), View.OnClickListener { DataManager.getInstance().register(busInfoBean) .compose(DataManager.setThread()) - .subscribe(BaseObserver(mContext,object : RxHttpCallBack(){ + .subscribe(BaseObserver(mContext, object : RxHttpCallBack() { override fun onSuccess(t: CommonResponseBean?) { super.onSuccess(t) ActivityUtils.finishOtherActivities(LoginActivity::class.java) } })) } + + + private var options1Items = ArrayList() + private val options2Items = ArrayList>() + private val options3Items = ArrayList>>() + private val MSG_LOAD_DATA = 0x0001 + private val MSG_LOAD_SUCCESS = 0x0002 + private val MSG_LOAD_FAILED = 0x0003 + private var thread: Thread? = null + private var isLoaded = false + + + private fun setProText() { + val pvOptions: OptionsPickerView<*> = OptionsPickerBuilder(this + ) { options1, options2, options3, v -> + //返回的分别是三个级别的选中位置 + val opt1tx = + if (options1Items.size > 0) options1Items.get(options1).getPickerViewText() else "" + val opt2tx = if (options2Items.size > 0 + && options2Items.get(options1).size > 0 + ) options2Items.get(options1).get(options2) else "" + val opt3tx = + if (options2Items.size > 0 && options3Items.get(options1).size > 0 && options3Items.get( + options1).get(options2).size > 0 + ) options3Items.get(options1).get(options2).get(options3) else "" + val tx = "$opt1tx-$opt2tx-$opt3tx" + etDz.text = tx + } + .setSubmitColor(Color.parseColor("#F1474B")) //确定按钮文字颜色 + .setCancelColor(Color.parseColor("#F1474B")) //取消按钮文字颜色 + .setTitleBgColor(Color.parseColor("#FFFFFF")) + .setBgColor(Color.parseColor("#F5F5F5")) + .setContentTextSize(20) + .build() + pvOptions.setPicker(options1Items as List?, + options2Items as List?, options3Items as List?) //三级选择器 + pvOptions.show() + } + + private fun initJsonData() { //解析数据 + /** + * 注意:assets 目录下的Json文件仅供参考,实际使用可自行替换文件 + * 关键逻辑在于循环体 + * + */ + val JsonData: String = + GetJsonDataUtil().getJson(this, "province.json") //获取assets目录下的json文件数据 + // String JsonData1 = new GetJsonDataUtil().getJson(this, "city.json");//获取assets目录下的json文件数据 + val jsonBean: ArrayList = parseData(JsonData) //用Gson 转成实体 + // ArrayList data = getData(JsonData1); + /** + * 添加省份数据 + * + * 注意:如果是添加的JavaBean实体,则实体类需要实现 IPickerViewData 接口, + * PickerView会通过getPickerViewText方法获取字符串显示出来。 + */ + options1Items = jsonBean + for (i in jsonBean.indices) { //遍历省份 + val cityList = ArrayList() //该省的城市列表(第二级) + val province_AreaList = ArrayList>() //该省的所有地区列表(第三极) + for (c in 0 until jsonBean[i].cityList.size) { //遍历该省份的所有城市 + val cityName = jsonBean[i].cityList[c].name + cityList.add(cityName) //添加城市 + val city_AreaList = ArrayList() //该城市的所有地区列表 + + //如果无地区数据,建议添加空字符串,防止数据为null 导致三个选项长度不匹配造成崩溃 + /*if (jsonBean.get(i).getCityList().get(c).getArea() == null + || jsonBean.get(i).getCityList().get(c).getArea().size() == 0) { + city_AreaList.add(""); + } else { + city_AreaList.addAll(jsonBean.get(i).getCityList().get(c).getArea()); + }*/city_AreaList.addAll(jsonBean[i].cityList[c].area) + province_AreaList.add(city_AreaList) //添加该省所有地区数据 + } + /** + * 添加城市数据 + */ + options2Items.add(cityList) + /** + * 添加地区数据 + */ + options3Items.add(province_AreaList) + } + mHandler.sendEmptyMessage(MSG_LOAD_SUCCESS) + } + + fun parseData(result: String): ArrayList { //Gson 解析 + val detail = ArrayList() + try { + val data = JSONArray(result) + val gson = Gson() + for (i in 0 until data.length()) { + val entity = gson.fromJson(data.optJSONObject(i).toString(), JsonBean::class.java) + detail.add(entity) + } + } catch (e: Exception) { + e.printStackTrace() + mHandler.sendEmptyMessage(MSG_LOAD_FAILED) + } + return detail + } + + @SuppressLint("HandlerLeak") + private val mHandler: Handler = object : Handler() { + override fun handleMessage(msg: Message) { + when (msg.what) { + MSG_LOAD_DATA -> if (thread == null) { //如果已创建就不再重新创建子线程了 + thread = Thread { // 子线程中解析省市区数据 + initJsonData() + } + thread?.start() + } + MSG_LOAD_SUCCESS -> { + // Toast.makeText(MyInfoActivity.this, "Parse Succeed", Toast.LENGTH_SHORT).show(); + setProText() + isLoaded = true + } + MSG_LOAD_FAILED -> Toast.makeText(this@BusinessInfoActivity, + "地区解析失败", + Toast.LENGTH_SHORT).show() + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/dahe/pass/ui/gone/activity/AddGoActivity.kt b/app/src/main/java/com/dahe/pass/ui/gone/activity/AddGoActivity.kt index 372229d..4d96168 100644 --- a/app/src/main/java/com/dahe/pass/ui/gone/activity/AddGoActivity.kt +++ b/app/src/main/java/com/dahe/pass/ui/gone/activity/AddGoActivity.kt @@ -26,6 +26,7 @@ import com.dahe.pass.net.RxHttpCallBack import com.dahe.pass.utils.GetJsonDataUtil import com.dahe.pass.utils.SPUtils import com.google.gson.Gson +import com.king.keyboard.KingKeyboard import com.lxj.xpopup.util.KeyboardUtils import kotlinx.android.synthetic.main.activity_add_go.* import org.json.JSONArray @@ -39,6 +40,8 @@ import java.util.* * @Description TODO */ class AddGoActivity : BaseActivity(), View.OnClickListener { + private lateinit var kingKeyboard : KingKeyboard + override fun getLayout() = R.layout.activity_add_go override fun initView(savedInstanceState: Bundle?) { @@ -50,13 +53,29 @@ class AddGoActivity : BaseActivity(), View.OnClickListener { tvStartTime.setOnClickListener(this) tvEndTime.setOnClickListener(this) tvSflx.setOnClickListener(this) - tvSTime.text = SPUtils.get(mContext,AppConfig.START_TIME, "").toString() - tvETime.text = SPUtils.get(mContext,AppConfig.END_TIME, "").toString() +// tvSTime.text = SPUtils.get(mContext,AppConfig.START_TIME, "").toString() +// tvETime.text = SPUtils.get(mContext,AppConfig.END_TIME, "").toString() + + kingKeyboard = KingKeyboard(this,keyboardParent) + kingKeyboard.register(etCarNum,KingKeyboard.KeyboardType.LICENSE_PLATE_PROVINCE) } override fun initDate() { } + /** + * 在Activity或Fragment的生命周期中调用对应的方法 + */ + override fun onResume() { + super.onResume() + kingKeyboard.onResume() + } + + override fun onDestroy() { + super.onDestroy() + kingKeyboard.onDestroy() + } + override fun onClick(view: View) { super.onClick(view) when (view.id) { @@ -73,10 +92,10 @@ class AddGoActivity : BaseActivity(), View.OnClickListener { fun sub() { KeyboardUtils.hideSoftInput(tvTxbh) - if (tvTxbh.text.toString().isNullOrEmpty() || tvSTime.text.toString().isNullOrEmpty() || + if (tvTxbh.text.toString().isNullOrEmpty() || etCarNum.text.toString().isNullOrEmpty() || etName.text.toString().isNullOrEmpty() || etPhone.text.toString().isNullOrEmpty() || etWpzl.text.toString().isNullOrEmpty() || - etDs.text.toString().isNullOrEmpty() || etQd.text.toString().isNullOrEmpty()|| tvSflx.text.toString().isNullOrEmpty() + etDs.text.toString().isNullOrEmpty() || etQd.text.toString().isNullOrEmpty()|| etCardId.text.toString().isNullOrEmpty() ) { showToast("请完善信息") return @@ -84,7 +103,7 @@ class AddGoActivity : BaseActivity(), View.OnClickListener { val passBean = PassBean() - passBean.passNo = tvTxbh.text.toString() + passBean.passNo = """FG-${tvTxbh.text.toString()}""" passBean.vehicle = etCarNum.text.toString() passBean.driverName = etName.text.toString() passBean.driverPhone = etPhone.text.toString() @@ -92,12 +111,13 @@ class AddGoActivity : BaseActivity(), View.OnClickListener { passBean.transportTonnage = etDs.text.toString() passBean.startPoint = etQd.text.toString() passBean.endPoint = etZd.text.toString() + passBean.driverIdcard = etCardId.text.toString() passBean.useStartTime = tvSTime.text.toString() - passBean.useEndTime = tvETime.text.toString() +// passBean.useEndTime = tvETime.text.toString() passBean.startTime = tvStartTime.text.toString() passBean.endTime = tvEndTime.text.toString() passBean.remarks = tvBz.text.toString() - passBean.chargeType = tvSflx.text.toString() +// passBean.chargeType = tvSflx.text.toString() DataManager.getInstance().addPass(passBean) .compose(DataManager.setThread()) .subscribe(BaseObserver(mContext, object : RxHttpCallBack() { diff --git a/app/src/main/java/com/dahe/pass/ui/gone/activity/EditGoActivity.kt b/app/src/main/java/com/dahe/pass/ui/gone/activity/EditGoActivity.kt index a9113a8..fcbef85 100644 --- a/app/src/main/java/com/dahe/pass/ui/gone/activity/EditGoActivity.kt +++ b/app/src/main/java/com/dahe/pass/ui/gone/activity/EditGoActivity.kt @@ -67,6 +67,7 @@ class EditGoActivity : BaseActivity(), View.OnClickListener { tvStartTime.text = passBean.startTime tvEndTime.text = passBean.endTime tvSflx.text = passBean.chargeType + etCardId.text = passBean.driverIdcard } } diff --git a/app/src/main/java/com/dahe/pass/ui/home/activity/ApplyActivity.kt b/app/src/main/java/com/dahe/pass/ui/home/activity/ApplyActivity.kt index 3b0bc1e..b006d8d 100644 --- a/app/src/main/java/com/dahe/pass/ui/home/activity/ApplyActivity.kt +++ b/app/src/main/java/com/dahe/pass/ui/home/activity/ApplyActivity.kt @@ -55,7 +55,7 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { override fun initDate() { DataManager.getInstance().userInfo .compose(DataManager.setThread()) - .subscribe(BaseObserver(mContext,object :RxHttpCallBack(){ + .subscribe(BaseObserver(mContext, object : RxHttpCallBack() { override fun onSuccess(t: CommonResponseBean?) { super.onSuccess(t) etQymc.text = t?.data?.enterpriseName @@ -112,8 +112,8 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { // Toast.makeText(this@MainActivity, str, Toast.LENGTH_SHORT).show() } .build() - mutableListOf.add(CompanyBean("免费","0")) - mutableListOf.add(CompanyBean("收费","1")) + mutableListOf.add(CompanyBean("免费", "0")) + mutableListOf.add(CompanyBean("收费", "1")) @@ -131,7 +131,7 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { jb = mutableListOf.get(options1).signName tvJb.text = mutableListOf.get(options1).signName } - .setTitleText("国家A级") + .setTitleText("国家等级") .setContentTextSize(20) //设置滚轮文字大小 .setDividerColor(Color.LTGRAY) //设置分割线的颜色 .setSelectOptions(0, 1) //默认选中项 @@ -151,11 +151,11 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { // Toast.makeText(this@MainActivity, str, Toast.LENGTH_SHORT).show() } .build() - mutableListOf.add(CompanyBean("否")) - mutableListOf.add(CompanyBean("A")) - mutableListOf.add(CompanyBean("AA")) - mutableListOf.add(CompanyBean("AAA")) - mutableListOf.add(CompanyBean("AAAA")) + mutableListOf.add(CompanyBean("2A")) + mutableListOf.add(CompanyBean("3A")) + mutableListOf.add(CompanyBean("4A")) + mutableListOf.add(CompanyBean("5A")) + mutableListOf.add(CompanyBean("其他")) @@ -235,7 +235,7 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { val mutableListOf = mutableListOf() val build = OptionsPickerBuilder(mContext ) { options1, options2, options3, v -> //返回的分别是三个级别的选中位置 - if (options1>=mutableListOf.size) + if (options1 >= mutableListOf.size) return@OptionsPickerBuilder curCompanyType = mutableListOf.get(options1) tvQylx.text = mutableListOf.get(options1).enterpriseTypeName @@ -277,10 +277,10 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { fun sub() { - if (!::curCompanyBean.isInitialized || !::curCompanyType.isInitialized || !::jb.isInitialized || + if (!::curCompanyBean.isInitialized || !::jb.isInitialized || !::shi.isInitialized || etQymc.text.toString().isNullOrEmpty() || etLxr.text.toString().isNullOrEmpty() || etPhone.text.toString().isNullOrEmpty() || - etCl.text.toString().isNullOrEmpty() || etTxz.text.toString().isNullOrEmpty()|| tvSflx.text.toString().isNullOrEmpty() + etCl.text.toString().isNullOrEmpty() || etTxz.text.toString().isNullOrEmpty() ) { showToast("请完善信息") return @@ -289,7 +289,7 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { val applySendBean = ApplySendBean() applySendBean.sign = curCompanyBean.sign applySendBean.enterpriseName = etQymc.text.toString() - applySendBean.typeId = curCompanyType.typeId +// applySendBean.typeId = curCompanyType.typeId applySendBean.provinceName = sheng applySendBean.cityName = shi applySendBean.countyName = xian @@ -298,7 +298,7 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { applySendBean.enterpriseContactPhone = etPhone.text.toString() applySendBean.vehiclesNumber = etCl.text.toString() applySendBean.numberOfPassesRequired = etTxz.text.toString() - applySendBean.chargeType = tvSflx.text.toString() +// applySendBean.chargeType = tvSflx.text.toString() DataManager.getInstance().applySend(applySendBean) .compose(DataManager.setThread()) @@ -361,7 +361,7 @@ class ApplyActivity : BaseActivity(), View.OnClickListener { .setCancelColor(Color.parseColor("#F1474B")) //取消按钮文字颜色 .setTitleBgColor(Color.parseColor("#FFFFFF")) .setBgColor(Color.parseColor("#F5F5F5")) - .setSelectOptions(15,0,0) + .setSelectOptions(15, 0, 0) .setContentTextSize(20) .build() pvOptions.setPicker(options1Items as List?, diff --git a/app/src/main/res/drawable-xxhdpi/nsr.png b/app/src/main/res/drawable-xxhdpi/nsr.png new file mode 100644 index 0000000000000000000000000000000000000000..c78a2b497223e833996ca9d973a388b339d0a5ab GIT binary patch literal 2020 zcmVPx+o=HSORCr$Hn+L3IMHPlWFn}6GkP<~9fCvd7f|P)OfRre`Hw{e@DFRBDrU|_o znn7s^oe-)(f>a?C3kaxm=~k4EC7|RZd*AHLo7pq7?>X<~=B?!9zW2_|T5JBhX3bjv z%$tRoTxOZc*G~?B4S^MaHKr&~&jL3Cm!|yB0dPC8<`i9g>-OJ)#er92>&OA{0`Tc6 z4uI~j1>7+NfbRh(PjLY52UZ@s1&omYO>qFW0InJefb&iT_Dv{w8Tj1{253HDse}(t z0iW&jFP8(HyAbePLdmYcIWri5jezSCK70%K>0|+L{*{2C6pD$(1T!P$24MY&1OEi( z$vIXM0qR&4xI3Y&caP;97{T}dPv|%pI6kMp6ae*@O;?I2G{MB8BWHUQ-)q}YH2`eeQ&Cwh{$>;_s?_mwV4cXE6M+LubkzW$zCQtX_dXfKp&fXzx5 z@3n zd~6bnj`{vt1BA!jZOwpR0xJfC-4fNj#ZUk=6m2hS&>t}N#-_1}mEV##R)tkE6abBQ z?th`m+V}?l9$0#a`pE$Bb>PAZqn6UQHq4CdO#AWpY(J7OGEmD_=ym80mA*`KzMq5wP4=sO@WpwjFKvb0+8bhO@?Rmk&y+9YKLss9J^o4 z%#i@~6!=Sp?m@@k6~I`< z5c>KK^GvKkin?911=a@K%Q5LC9tZrkb5jJ`0x2c?my)?P0JwNT2<3U;Gr&7JuSfj7 zQSg(5PT3&)gg@kLmI7ed8~&l#7=5WBOLDfl-e?G8NVvZH7C2~?q07w-)zXCj(YD2#-k)E08H2cF2+)BwWCA4V-7zv@ITJ&OLa{;eJ6#o`j zu6E^32WKbO%2~}H_UQcEdVpMCt=!qxSWrdW#1>4}Gz?-=h2j4H)9D5%L z{#{~anV|s0ZiFO2+dW+4;|9RbIunenJ)6Iq{JPt~D2WlCIeXUw@&a9v9hM~?6ah9h zHW*!b82Eh7lgRfWm;xffp?I~`B{Sq2ylbeHRK(7aB6D*&KrSvkUWVKRNt1P7Xc+!p zy8gRa;RgXL%I<04jc>M+3lK6dof1LK8pdvM8p#15&p&%G%XgHeh{XQ590?GQ_d>3=?`v z05}FXBw|^I4Cm+*)aI5pe4po0S8_iPK5aP`_q;5ZAE?Gs3PN054`l!@Are4RS`y;={C z#Aytc0^n7&rKJ2|C^9@UMk&gCB&$p)NiF`}rin9u^-3uKD(U0$%WbfZ-dC29fCHzK3&7z+Jwv1lae-Oa=hwTh|`}_%6RjBv!p|Xg@xC?LL($)g=ikbsu{(SfpdJ z0JKKDt~MG5Jya_4blO2fsJUshj0yl1`=1j8VHdz`JuZ>-kf*465SemjlmN6g?7lW{ znxT?5wiGDq3l~1pigO$QbQdI_;F}%)uR-nh@Wq$phOb)W?AjealSR0@N0SkF!JBtC=|~00K5ldo=;F~AqSBF0000 - - + android:focusable="true" + android:focusableInTouchMode="true" + android:orientation="vertical"> + - - - + android:layout_height="0dp" + android:layout_weight="1" + android:fillViewport="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:gravity="center_vertical" + android:orientation="vertical" + android:padding="6dp"> + + + + + + + + + + + - - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_apply.xml b/app/src/main/res/layout/activity_apply.xml index 9eb7c8c..bbf05a4 100644 --- a/app/src/main/res/layout/activity_apply.xml +++ b/app/src/main/res/layout/activity_apply.xml @@ -44,6 +44,7 @@ diff --git a/app/src/main/res/layout/activity_business_info.xml b/app/src/main/res/layout/activity_business_info.xml index 9755458..552edb9 100644 --- a/app/src/main/res/layout/activity_business_info.xml +++ b/app/src/main/res/layout/activity_business_info.xml @@ -54,7 +54,7 @@ android:layout_marginLeft="10dp" android:background="@null" android:ellipsize="end" - android:hint="企业名称,必填" + android:hint="企业名称" android:maxLength="11" android:maxLines="1"> @@ -84,7 +84,38 @@ android:layout_marginLeft="10dp" android:background="@null" android:ellipsize="end" - android:hint="管理人姓名,必填" + android:hint="法人人姓名" + android:maxLength="11" + android:maxLines="1"> + + + + + + + + + @@ -115,7 +146,7 @@ android:layout_marginLeft="10dp" android:background="@null" android:ellipsize="end" - android:hint="负责人,必填" + android:hint="通行证管理人" android:maxLength="11" android:maxLines="1"> @@ -146,7 +177,7 @@ android:layout_marginLeft="10dp" android:background="@null" android:ellipsize="end" - android:hint="负责人电话,必填" + android:hint="管理人电话" android:maxLength="11" android:maxLines="1"> @@ -173,7 +204,9 @@ android:layout_height="wrap_content" android:background="@drawable/dj"> - + android:maxLines="1"> @@ -235,16 +271,19 @@ android:layout_height="wrap_content" android:background="@drawable/dz"> - + android:textColor="@color/black" + android:maxLines="1"> @@ -274,13 +313,14 @@ android:layout_weight="1" android:background="@null" android:ellipsize="end" - android:hint="公司地址" + android:hint="公司园区地址" android:maxLines="1"> + + + + + + + + + + + + + diff --git a/keybordlib/src/main/java/com/king/keyboard/Keyboard.java b/keybordlib/src/main/java/com/king/keyboard/Keyboard.java new file mode 100644 index 0000000..eca95de --- /dev/null +++ b/keybordlib/src/main/java/com/king/keyboard/Keyboard.java @@ -0,0 +1,913 @@ +package com.king.keyboard; + +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; + +import androidx.annotation.XmlRes; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + + +/** + * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard + * consists of rows of keys. + *

The layout file for a keyboard contains XML that looks like the following snippet:

+ *
+ * <Keyboard
+ *         android:keyWidth="%10p"
+ *         android:keyHeight="50px"
+ *         android:horizontalGap="2px"
+ *         android:verticalGap="2px" >
+ *     <Row android:keyWidth="32px" >
+ *         <Key android:keyLabel="A" />
+ *         ...
+ *     </Row>
+ *     ...
+ * </Keyboard>
+ * 
+ * @attr ref android.R.styleable#King_Keyboard_keyWidth + * @attr ref android.R.styleable#King_Keyboard_keyHeight + * @attr ref android.R.styleable#King_Keyboard_horizontalGap + * @attr ref android.R.styleable#King_Keyboard_verticalGap + * This class is deprecated because this is just a convenient UI widget class that + * application developers can re-implement on top of existing public APIs. If you have + * already depended on this class, consider copying the implementation from AOSP into + * your project or re-implementing a similar widget by yourselves + */ +public class Keyboard { + + static final String TAG = "Keyboard"; + + // Keyboard XML Tags + private static final String TAG_KEYBOARD = "Keyboard"; + private static final String TAG_ROW = "Row"; + private static final String TAG_KEY = "Key"; + + public static final int EDGE_LEFT = 0x01; + public static final int EDGE_RIGHT = 0x02; + public static final int EDGE_TOP = 0x04; + public static final int EDGE_BOTTOM = 0x08; + + 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; + + /** Keyboard label **/ + private CharSequence mLabel; + + /** Horizontal gap default for all rows */ + private int mDefaultHorizontalGap; + + /** Default key width */ + private int mDefaultWidth; + + /** Default key height */ + private int mDefaultHeight; + + /** Default gap between rows */ + private int mDefaultVerticalGap; + + /** Is the keyboard in the shifted state */ + private boolean mShifted; + + /** Key instance for the shift key, if present */ + private Key[] mShiftKeys = { null, null }; + + /** Key index for the shift key, if present */ + private int[] mShiftKeyIndices = {-1, -1}; + + /** Current key width, while loading the keyboard */ + private int mKeyWidth; + + /** Current key height, while loading the keyboard */ + private int mKeyHeight; + + /** Total height of the keyboard, including the padding and keys */ + private int mTotalHeight; + + /** + * Total width of the keyboard, including left side gaps and keys, but not any gaps on the + * right side. + */ + private int mTotalWidth; + + /** List of keys in this keyboard */ + private List mKeys; + + /** List of modifier keys such as Shift & Alt, if any */ + private List mModifierKeys; + + /** Width of the screen available to fit the keyboard */ + private int mDisplayWidth; + + /** Height of the screen */ + private int mDisplayHeight; + + /** Keyboard mode, or zero, if none. */ + private int mKeyboardMode; + + // Variables for pre-computing nearest keys. + + private static final int GRID_WIDTH = 10; + private static final int GRID_HEIGHT = 5; + private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; + private int mCellWidth; + private int mCellHeight; + private int[][] mGridNeighbors; + private int mProximityThreshold; + /** Number of key widths from current touch point to search for nearest keys. */ + private static float SEARCH_DISTANCE = 1.8f; + + private ArrayList rows = new ArrayList<>(); + + /** + * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. + * Some of the key size defaults can be overridden per row from what the {@link Keyboard} + * defines. + * @attr ref android.R.styleable#King_Keyboard_keyWidth + * @attr ref android.R.styleable#King_Keyboard_keyHeight + * @attr ref android.R.styleable#King_Keyboard_horizontalGap + * @attr ref android.R.styleable#King_Keyboard_verticalGap + * @attr ref android.R.styleable#King_Keyboard_Row_rowEdgeFlags + * @attr ref android.R.styleable#King_Keyboard_Row_keyboardMode + */ + public static class Row { + /** Default width of a key in this row. */ + public int defaultWidth; + /** Default height of a key in this row. */ + public int defaultHeight; + /** Default horizontal gap between keys in this row. */ + public int defaultHorizontalGap; + /** Vertical gap following this row. */ + public int verticalGap; + + ArrayList mKeys = new ArrayList<>(); + + /** + * Edge flags for this row of keys. Possible values that can be assigned are + * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} + */ + public int rowEdgeFlags; + + /** The keyboard mode for this row */ + public int mode; + + private Keyboard parent; + + public Row(Keyboard parent) { + this.parent = parent; + } + + public Row(Resources res, Keyboard parent, XmlResourceParser parser) { + this.parent = parent; + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.King_Keyboard); + defaultWidth = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_keyWidth, + parent.mDisplayWidth, parent.mDefaultWidth); + defaultHeight = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_keyHeight, + parent.mDisplayHeight, parent.mDefaultHeight); + defaultHorizontalGap = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_horizontalGap, + parent.mDisplayWidth, parent.mDefaultHorizontalGap); + verticalGap = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_verticalGap, + parent.mDisplayHeight, parent.mDefaultVerticalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.King_Keyboard_Row); + rowEdgeFlags = a.getInt(R.styleable.King_Keyboard_Row_android_rowEdgeFlags, 0); + mode = a.getResourceId(R.styleable.King_Keyboard_Row_android_keyboardMode, + 0); + } + } + + /** + * Class for describing the position and characteristics of a single key in the keyboard. + * + * @attr ref android.R.styleable#King_Keyboard_keyWidth + * @attr ref android.R.styleable#King_Keyboard_keyHeight + * @attr ref android.R.styleable#King_Keyboard_horizontalGap + * @attr ref android.R.styleable#King_Keyboard_Key_codes + * @attr ref android.R.styleable#King_Keyboard_Key_keyIcon + * @attr ref android.R.styleable#King_Keyboard_Key_keyLabel + * @attr ref android.R.styleable#King_Keyboard_Key_iconPreview + * @attr ref android.R.styleable#King_Keyboard_Key_isSticky + * @attr ref android.R.styleable#King_Keyboard_Key_isRepeatable + * @attr ref android.R.styleable#King_Keyboard_Key_isModifier + * @attr ref android.R.styleable#King_Keyboard_Key_popupKeyboard + * @attr ref android.R.styleable#King_Keyboard_Key_popupCharacters + * @attr ref android.R.styleable#King_Keyboard_Key_keyOutputText + * @attr ref android.R.styleable#King_Keyboard_Key_keyEdgeFlags + */ + public static class Key { + /** + * All the key codes (unicode or custom code) that this key could generate, zero'th + * being the most important. + */ + public int[] codes; + + /** Label to display */ + public CharSequence label; + + /** Icon to display instead of a label. Icon takes precedence over a label */ + public Drawable icon; + /** Preview version of the icon, for the preview popup */ + public Drawable iconPreview; + /** Width of the key, not including the gap */ + public int width; + /** Height of the key, not including the gap */ + public int height; + /** The horizontal gap before this key */ + public int gap; + /** Whether this key is sticky, i.e., a toggle key */ + public boolean sticky; + /** X coordinate of the key in the keyboard layout */ + public int x; + /** Y coordinate of the key in the keyboard layout */ + public int y; + /** The current pressed state of this key */ + public boolean pressed; + /** If this is a sticky key, is it on? */ + public boolean on; + /** Text to output when pressed. This can be multiple characters, like ".com" */ + public CharSequence text; + /** Popup characters */ + public CharSequence popupCharacters; + + /** + * Flags that specify the anchoring to edges of the keyboard for detecting touch events + * that are just out of the boundary of the key. This is a bit mask of + * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and + * {@link Keyboard#EDGE_BOTTOM}. + */ + public int edgeFlags; + /** Whether this is a modifier key, such as Shift or Alt */ + public boolean modifier; + /** The keyboard that this key belongs to */ + private Keyboard keyboard; + /** + * If this key pops up a mini keyboard, this is the resource id for the XML layout for that + * keyboard. + */ + public int popupResId; + /** Whether this key repeats itself when held down */ + public boolean repeatable; + + + private final static int[] KEY_STATE_NORMAL_ON = { + android.R.attr.state_checkable, + android.R.attr.state_checked + }; + + private final static int[] KEY_STATE_PRESSED_ON = { + android.R.attr.state_pressed, + android.R.attr.state_checkable, + android.R.attr.state_checked + }; + + private final static int[] KEY_STATE_NORMAL_OFF = { + android.R.attr.state_checkable + }; + + private final static int[] KEY_STATE_PRESSED_OFF = { + android.R.attr.state_pressed, + android.R.attr.state_checkable + }; + + private final static int[] KEY_STATE_NORMAL = { + }; + + private final static int[] KEY_STATE_PRESSED = { + android.R.attr.state_pressed + }; + + /** Create an empty key with no attributes. */ + public Key(Row parent) { + keyboard = parent.parent; + height = parent.defaultHeight; + width = parent.defaultWidth; + gap = parent.defaultHorizontalGap; + edgeFlags = parent.rowEdgeFlags; + } + + /** Create a key with the given top-left coordinate and extract its attributes from + * the XML parser. + * @param res resources associated with the caller's context + * @param parent the row that this key belongs to. The row must already be attached to + * a {@link Keyboard}. + * @param x the x coordinate of the top-left + * @param y the y coordinate of the top-left + * @param parser the XML parser containing the attributes for this key + */ + public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) { + this(parent); + + this.x = x; + this.y = y; + + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.King_Keyboard); + + width = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_keyWidth, + keyboard.mDisplayWidth, parent.defaultWidth); + height = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_keyHeight, + keyboard.mDisplayHeight, parent.defaultHeight); + gap = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_horizontalGap, + keyboard.mDisplayWidth, parent.defaultHorizontalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.King_Keyboard_Key); + this.x += gap; + TypedValue codesValue = new TypedValue(); + a.getValue(R.styleable.King_Keyboard_Key_android_codes, + codesValue); + if (codesValue.type == TypedValue.TYPE_INT_DEC + || codesValue.type == TypedValue.TYPE_INT_HEX) { + codes = new int[] { codesValue.data }; + } else if (codesValue.type == TypedValue.TYPE_STRING) { + codes = parseCSV(codesValue.string.toString()); + } + + iconPreview = a.getDrawable(R.styleable.King_Keyboard_Key_android_iconPreview); + if (iconPreview != null) { + iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(), + iconPreview.getIntrinsicHeight()); + } + popupCharacters = a.getText( + R.styleable.King_Keyboard_Key_android_popupCharacters); + popupResId = a.getResourceId( + R.styleable.King_Keyboard_Key_android_popupKeyboard, 0); + repeatable = a.getBoolean( + R.styleable.King_Keyboard_Key_android_isRepeatable, false); + modifier = a.getBoolean( + R.styleable.King_Keyboard_Key_android_isModifier, false); + sticky = a.getBoolean( + R.styleable.King_Keyboard_Key_android_isSticky, false); + edgeFlags = a.getInt(R.styleable.King_Keyboard_Key_android_keyEdgeFlags, 0); + edgeFlags |= parent.rowEdgeFlags; + + icon = a.getDrawable( + R.styleable.King_Keyboard_Key_android_keyIcon); + if (icon != null) { + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + } + label = a.getText(R.styleable.King_Keyboard_Key_android_keyLabel); + text = a.getText(R.styleable.King_Keyboard_Key_android_keyOutputText); + + if (codes == null && !TextUtils.isEmpty(label)) { + codes = new int[] { label.charAt(0) }; + } + a.recycle(); + } + + /** + * Informs the key that it has been pressed, in case it needs to change its appearance or + * state. + * @see #onReleased(boolean) + */ + public void onPressed() { + pressed = !pressed; + } + + /** + * Changes the pressed state of the key. + * + *

Toggled state of the key will be flipped when all the following conditions are + * fulfilled:

+ * + *
    + *
  • This is a sticky key, that is, {@link #sticky} is {@code true}. + *
  • The parameter {@code inside} is {@code true}. + *
  • {@link android.os.Build.VERSION#SDK_INT} is greater than + * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}. + *
+ * + * @param inside whether the finger was released inside the key. Works only on Android M and + * later. See the method document for details. + * @see #onPressed() + */ + public void onReleased(boolean inside) { + pressed = !pressed; + if (sticky && inside) { + on = !on; + } + } + + int[] parseCSV(String value) { + int count = 0; + int lastIndex = 0; + if (value.length() > 0) { + count++; + while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) { + count++; + } + } + int[] values = new int[count]; + count = 0; + StringTokenizer st = new StringTokenizer(value, ","); + while (st.hasMoreTokens()) { + try { + values[count++] = Integer.parseInt(st.nextToken()); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Error parsing keycodes " + value); + } + } + return values; + } + + /** + * Detects if a point falls inside this key. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return whether or not the point falls inside the key. If the key is attached to an edge, + * it will assume that all points between the key and the edge are considered to be inside + * the key. + */ + public boolean isInside(int x, int y) { + boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0; + boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0; + boolean topEdge = (edgeFlags & EDGE_TOP) > 0; + boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0; + if ((x >= this.x || (leftEdge && x <= this.x + this.width)) + && (x < this.x + this.width || (rightEdge && x >= this.x)) + && (y >= this.y || (topEdge && y <= this.y + this.height)) + && (y < this.y + this.height || (bottomEdge && y >= this.y))) { + return true; + } else { + return false; + } + } + + /** + * Returns the square of the distance between the center of the key and the given point. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the square of the distance of the point from the center of the key + */ + public int squaredDistanceFrom(int x, int y) { + int xDist = this.x + width / 2 - x; + int yDist = this.y + height / 2 - y; + return xDist * xDist + yDist * yDist; + } + + /** + * Returns the drawable state for the key, based on the current state and type of the key. + * @return the drawable state of the key. + * @see android.graphics.drawable.StateListDrawable#setState(int[]) + */ + public int[] getCurrentDrawableState() { + int[] states = KEY_STATE_NORMAL; + + if (on) { + if (pressed) { + states = KEY_STATE_PRESSED_ON; + } else { + states = KEY_STATE_NORMAL_ON; + } + } else { + if (sticky) { + if (pressed) { + states = KEY_STATE_PRESSED_OFF; + } else { + states = KEY_STATE_NORMAL_OFF; + } + } else { + if (pressed) { + states = KEY_STATE_PRESSED; + } + } + } + return states; + } + } + + /** + * Creates a keyboard from the given xml key layout file. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + */ + public Keyboard(Context context, int xmlLayoutResId) { + this(context, xmlLayoutResId, 0); + } + + /** + * Creates a keyboard from the given xml key layout file. Weeds out rows + * that have a keyboard mode defined but don't match the specified mode. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + * @param modeId keyboard mode identifier + * @param width sets width of keyboard + * @param height sets height of keyboard + */ + public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width, + int height) { + mDisplayWidth = width; + mDisplayHeight = height; + + mDefaultHorizontalGap = 0; + mDefaultWidth = mDisplayWidth / 10; + mDefaultVerticalGap = 0; + mDefaultHeight = mDefaultWidth; + mKeys = new ArrayList<>(); + mModifierKeys = new ArrayList<>(); + mKeyboardMode = modeId; + loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); + } + + /** + * Creates a keyboard from the given xml key layout file. Weeds out rows + * that have a keyboard mode defined but don't match the specified mode. + * @param context the application or service context + * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. + * @param modeId keyboard mode identifier + */ + public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + mDisplayWidth = dm.widthPixels; + mDisplayHeight = dm.heightPixels; + //Log.v(TAG, "keyboard's display metrics:" + dm); + + mDefaultHorizontalGap = 0; + mDefaultWidth = mDisplayWidth / 10; + mDefaultVerticalGap = 0; + mDefaultHeight = mDefaultWidth; + mKeys = new ArrayList<>(); + mModifierKeys = new ArrayList<>(); + mKeyboardMode = modeId; + loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); + } + + /** + *

Creates a blank keyboard from the given resource file and populates it with the specified + * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. + *

+ *

If the specified number of columns is -1, then the keyboard will fit as many keys as + * possible in each row.

+ * @param context the application or service context + * @param layoutTemplateResId the layout template file, containing no keys. + * @param characters the list of characters to display on the keyboard. One key will be created + * for each character. + * @param columns the number of columns of keys to display. If this number is greater than the + * number of keys that can fit in a row, it will be ignored. If this number is -1, the + * keyboard will fit as many keys as possible in each row. + */ + public Keyboard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + this(context, layoutTemplateResId); + int x = 0; + int y = 0; + int column = 0; + mTotalWidth = 0; + + Row row = new Row(this); + row.defaultHeight = mDefaultHeight; + row.defaultWidth = mDefaultWidth; + row.defaultHorizontalGap = mDefaultHorizontalGap; + row.verticalGap = mDefaultVerticalGap; + row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; + final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; + for (int i = 0; i < characters.length(); i++) { + char c = characters.charAt(i); + if (column >= maxColumns + || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { + x = 0; + y += mDefaultVerticalGap + mDefaultHeight; + column = 0; + } + final Key key = new Key(row); + key.x = x; + key.y = y; + key.label = String.valueOf(c); + key.codes = new int[] { c }; + column++; + x += key.width + key.gap; + mKeys.add(key); + row.mKeys.add(key); + if (x > mTotalWidth) { + mTotalWidth = x; + } + } + mTotalHeight = y + mDefaultHeight; + rows.add(row); + } + + final void resize(int newWidth, int newHeight) { + int numRows = rows.size(); + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + Row row = rows.get(rowIndex); + int numKeys = row.mKeys.size(); + int totalGap = 0; + int totalWidth = 0; + for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) { + Key key = row.mKeys.get(keyIndex); + if (keyIndex > 0) { + totalGap += key.gap; + } + totalWidth += key.width; + } + if (totalGap + totalWidth > newWidth) { + int x = 0; + float scaleFactor = (float)(newWidth - totalGap) / totalWidth; + for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) { + Key key = row.mKeys.get(keyIndex); + key.width *= scaleFactor; + key.x = x; + x += key.width + key.gap; + } + } + } + mTotalWidth = newWidth; + // TODO: This does not adjust the vertical placement according to the new size. + // The main problem in the previous code was horizontal placement/size, but we should + // also recalculate the vertical sizes/positions when we get this resize call. + } + + public List getKeys() { + return mKeys; + } + + public List getModifierKeys() { + return mModifierKeys; + } + + protected int getHorizontalGap() { + return mDefaultHorizontalGap; + } + + protected void setHorizontalGap(int gap) { + mDefaultHorizontalGap = gap; + } + + protected int getVerticalGap() { + return mDefaultVerticalGap; + } + + protected void setVerticalGap(int gap) { + mDefaultVerticalGap = gap; + } + + protected int getKeyHeight() { + return mDefaultHeight; + } + + protected void setKeyHeight(int height) { + mDefaultHeight = height; + } + + protected int getKeyWidth() { + return mDefaultWidth; + } + + protected void setKeyWidth(int width) { + mDefaultWidth = width; + } + + /** + * Returns the total height of the keyboard + * @return the total height of the keyboard + */ + public int getHeight() { + return mTotalHeight; + } + + public int getMinWidth() { + return mTotalWidth; + } + + public boolean setShifted(boolean shiftState) { + for (Key shiftKey : mShiftKeys) { + if (shiftKey != null) { + shiftKey.on = shiftState; + } + } + if (mShifted != shiftState) { + mShifted = shiftState; + return true; + } + return false; + } + + public boolean isShifted() { + return mShifted; + } + + /** + * @hide + */ + public int[] getShiftKeyIndices() { + return mShiftKeyIndices; + } + + public int getShiftKeyIndex() { + return mShiftKeyIndices[0]; + } + + private void computeNearestNeighbors() { + // Round-up so we don't have any pixels outside the grid + mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; + mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; + mGridNeighbors = new int[GRID_SIZE][]; + int[] indices = new int[mKeys.size()]; + final int gridWidth = GRID_WIDTH * mCellWidth; + final int gridHeight = GRID_HEIGHT * mCellHeight; + for (int x = 0; x < gridWidth; x += mCellWidth) { + for (int y = 0; y < gridHeight; y += mCellHeight) { + int count = 0; + for (int i = 0; i < mKeys.size(); i++) { + final Key key = mKeys.get(i); + if (key.squaredDistanceFrom(x, y) < mProximityThreshold || + key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold || + key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) + < mProximityThreshold || + key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) { + indices[count++] = i; + } + } + int [] cell = new int[count]; + System.arraycopy(indices, 0, cell, 0, count); + mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; + } + } + } + + /** + * Returns the indices of the keys that are closest to the given point. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the array of integer indices for the nearest keys to the given point. If the given + * point is out of range, then an array of size zero is returned. + */ + public int[] getNearestKeys(int x, int y) { + if (mGridNeighbors == null) computeNearestNeighbors(); + if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { + int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); + if (index < GRID_SIZE) { + return mGridNeighbors[index]; + } + } + return new int[0]; + } + + protected Row createRowFromXml(Resources res, XmlResourceParser parser) { + return new Row(res, this, parser); + } + + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser) { + return new Key(res, parent, x, y, parser); + } + + private void loadKeyboard(Context context, XmlResourceParser parser) { + boolean inKey = false; + boolean inRow = false; + boolean leftMostKey = false; + int row = 0; + int x = 0; + int y = 0; + Key key = null; + Row currentRow = null; + Resources res = context.getResources(); + boolean skipRow = false; + + try { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.START_TAG) { + String tag = parser.getName(); + if (TAG_ROW.equals(tag)) { + inRow = true; + x = 0; + currentRow = createRowFromXml(res, parser); + rows.add(currentRow); + skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode; + if (skipRow) { + skipToEndOfRow(parser); + inRow = false; + } + } else if (TAG_KEY.equals(tag)) { + inKey = true; + key = createKeyFromXml(res, currentRow, x, y, parser); + mKeys.add(key); + if (key.codes[0] == KEYCODE_SHIFT) { + // Find available shift key slot and put this shift key in it + for (int i = 0; i < mShiftKeys.length; i++) { + if (mShiftKeys[i] == null) { + mShiftKeys[i] = key; + mShiftKeyIndices[i] = mKeys.size()-1; + break; + } + } + mModifierKeys.add(key); + } else if (key.codes[0] == KEYCODE_ALT) { + mModifierKeys.add(key); + } + currentRow.mKeys.add(key); + } else if (TAG_KEYBOARD.equals(tag)) { + parseKeyboardAttributes(res, parser); + } + } else if (event == XmlResourceParser.END_TAG) { + if (inKey) { + inKey = false; + x += key.gap + key.width; + if (x > mTotalWidth) { + mTotalWidth = x; + } + } else if (inRow) { + inRow = false; + y += currentRow.verticalGap; + y += currentRow.defaultHeight; + row++; + } else { + // TODO: error or extend? + } + } + } + } catch (Exception e) { + Log.e(TAG, "Parse error:" + e); + e.printStackTrace(); + } + mTotalHeight = y - mDefaultVerticalGap; + } + + private void skipToEndOfRow(XmlResourceParser parser) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.END_TAG + && parser.getName().equals(TAG_ROW)) { + break; + } + } + } + + private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) { + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.King_Keyboard); + + mDefaultWidth = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_keyWidth, + mDisplayWidth, mDisplayWidth / 10); + mDefaultHeight = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_keyHeight, + mDisplayHeight, 50); + mDefaultHorizontalGap = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_horizontalGap, + mDisplayWidth, 0); + mDefaultVerticalGap = getDimensionOrFraction(a, + R.styleable.King_Keyboard_android_verticalGap, + mDisplayHeight, 0); + mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE); + mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison + a.recycle(); + } + + static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { + TypedValue value = a.peekValue(index); + if (value == null) return defValue; + if (value.type == TypedValue.TYPE_DIMENSION) { + return a.getDimensionPixelOffset(index, defValue); + } else if (value.type == TypedValue.TYPE_FRACTION) { + // Round it to avoid values like 47.9999 from getting truncated + return Math.round(a.getFraction(index, base, base, defValue)); + } + return defValue; + } +} + diff --git a/keybordlib/src/main/java/com/king/keyboard/KeyboardView.java b/keybordlib/src/main/java/com/king/keyboard/KeyboardView.java new file mode 100644 index 0000000..89c3a64 --- /dev/null +++ b/keybordlib/src/main/java/com/king/keyboard/KeyboardView.java @@ -0,0 +1,1505 @@ +package com.king.keyboard; + +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup.LayoutParams; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.king.keyboard.Keyboard.Key; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and + * detecting key presses and touch movements. + * + * @attr ref android.R.styleable#King_KeyboardView_keyBackground + * @attr ref android.R.styleable#King_KeyboardView_keyPreviewLayout + * @attr ref android.R.styleable#King_KeyboardView_keyPreviewOffset + * @attr ref android.R.styleable#King_KeyboardView_keyPreviewHeight + * @attr ref android.R.styleable#King_KeyboardView_labelTextSize + * @attr ref android.R.styleable#King_KeyboardView_keyTextSize + * @attr ref android.R.styleable#King_KeyboardView_keyTextColor + * @attr ref android.R.styleable#King_KeyboardView_verticalCorrection + * @attr ref android.R.styleable#King_KeyboardView_popupLayout + * + * This class is deprecated because this is just a convenient UI widget class that + * application developers can re-implement on top of existing public APIs. If you have + * already depended on this class, consider copying the implementation from AOSP into + * your project or re-implementing a similar widget by yourselves + */ + +public class KeyboardView extends View implements View.OnClickListener { + + /** + * Listener for virtual keyboard events. + */ + public interface OnKeyboardActionListener { + + /** + * Called when the user presses a key. This is sent before the {@link #onKey} is called. + * For keys that repeat, this is only called once. + * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid + * key, the value will be zero. + */ + void onPress(int primaryCode); + + /** + * Called when the user releases a key. This is sent after the {@link #onKey} is called. + * For keys that repeat, this is only called once. + * @param primaryCode the code of the key that was released + */ + void onRelease(int primaryCode); + + /** + * Send a key press to the listener. + * @param primaryCode this is the key that was pressed + * @param keyCodes the codes for all the possible alternative keys + * with the primary code being the first. If the primary key code is + * a single character such as an alphabet or number or symbol, the alternatives + * will include other characters that may be on the same key or adjacent keys. + * These codes are useful to correct for accidental presses of a key adjacent to + * the intended key. + */ + void onKey(int primaryCode, int[] keyCodes); + + /** + * Sends a sequence of characters to the listener. + * @param text the sequence of characters to be displayed. + */ + void onText(CharSequence text); + + /** + * Called when the user quickly moves the finger from right to left. + */ + void swipeLeft(); + + /** + * Called when the user quickly moves the finger from left to right. + */ + void swipeRight(); + + /** + * Called when the user quickly moves the finger from up to down. + */ + void swipeDown(); + + /** + * Called when the user quickly moves the finger from down to up. + */ + void swipeUp(); + } + + private static final boolean DEBUG = false; + private static final int NOT_A_KEY = -1; + private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE }; + private static final int[] LONG_PRESSABLE_STATE_SET = { R.styleable.King_KeyboardViewPreviewState_android_state_long_pressable }; + + private Context mContext; + private Keyboard mKeyboard; + private int mCurrentKeyIndex = NOT_A_KEY; + + private int mLabelTextSize; + private int mKeyTextSize; + private int mKeyTextColor; + private float mShadowRadius; + private int mShadowColor; + private float mBackgroundDimAmount; + + private TextView mPreviewText; + private PopupWindow mPreviewPopup; + private int mPreviewTextSizeLarge; + private int mPreviewOffset; + private int mPreviewHeight; + // Working variable + private final int[] mCoordinates = new int[2]; + + private PopupWindow mPopupKeyboard; + private View mMiniKeyboardContainer; + private KeyboardView mMiniKeyboard; + private boolean mMiniKeyboardOnScreen; + private View mPopupParent; + private int mMiniKeyboardOffsetX; + private int mMiniKeyboardOffsetY; + private Map mMiniKeyboardCache; + private Key[] mKeys; + + /** Listener for {@link OnKeyboardActionListener}. */ + private OnKeyboardActionListener mKeyboardActionListener; + + private static final int MSG_SHOW_PREVIEW = 1; + private static final int MSG_REMOVE_PREVIEW = 2; + private static final int MSG_REPEAT = 3; + private static final int MSG_LONGPRESS = 4; + + private static final int DELAY_BEFORE_PREVIEW = 0; + private static final int DELAY_AFTER_PREVIEW = 70; + private static final int DEBOUNCE_TIME = 70; + + private int mVerticalCorrection; + private int mProximityThreshold; + + private boolean mPreviewCentered = false; + private boolean mShowPreview = true; + private boolean mShowTouchPoints = true; + private int mPopupPreviewX; + private int mPopupPreviewY; + + private int mLastX; + private int mLastY; + private int mStartX; + private int mStartY; + + private boolean mProximityCorrectOn; + + private Paint mPaint; + private Rect mPadding; + + private long mDownTime; + private long mLastMoveTime; + private int mLastKey; + private int mLastCodeX; + private int mLastCodeY; + private int mCurrentKey = NOT_A_KEY; + private int mDownKey = NOT_A_KEY; + private long mLastKeyTime; + private long mCurrentKeyTime; + private int[] mKeyIndices = new int[12]; + private GestureDetector mGestureDetector; + private int mPopupX; + private int mPopupY; + private int mRepeatKeyIndex = NOT_A_KEY; + private int mPopupLayout; + private boolean mAbortKey; + private Key mInvalidatedKey; + private Rect mClipRegion = new Rect(0, 0, 0, 0); + private boolean mPossiblePoly; + private SwipeTracker mSwipeTracker = new SwipeTracker(); + private int mSwipeThreshold; + private boolean mDisambiguateSwipe; + + // Variables for dealing with multiple pointers + private int mOldPointerCount = 1; + private float mOldPointerX; + private float mOldPointerY; + + private Drawable mKeyBackground; + + private static final int REPEAT_INTERVAL = 50; // ~20 keys per second + private static final int REPEAT_START_DELAY = 400; + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); + + private static int MAX_NEARBY_KEYS = 12; + private int[] mDistances = new int[MAX_NEARBY_KEYS]; + + // For multi-tap + private int mLastSentIndex; + private int mTapCount; + private long mLastTapTime; + private boolean mInMultiTap; + private static final int MULTITAP_INTERVAL = 800; // milliseconds + private StringBuilder mPreviewLabel = new StringBuilder(1); + + /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ + private boolean mDrawPending; + /** The dirty region in the keyboard bitmap */ + private Rect mDirtyRect = new Rect(); + /** The keyboard bitmap for faster updates */ + private Bitmap mBuffer; + /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ + private boolean mKeyboardChanged; + /** The canvas for the above mutable keyboard bitmap */ + private Canvas mCanvas; + /** The accessibility manager for accessibility support */ +// private AccessibilityManager mAccessibilityManager; + /** The audio manager for accessibility support */ + private AudioManager mAudioManager; + /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */ + private boolean mHeadsetRequiredToHearPasswordsAnnounced; + + Handler mHandler; + + public KeyboardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mContext = context; + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.King_KeyboardView, defStyleAttr, defStyleRes); + + LayoutInflater inflate = + (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + int previewLayout = 0; + int keyTextSize = 0; + + int n = a.getIndexCount(); + + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + if (attr == R.styleable.King_KeyboardView_android_keyBackground) { + mKeyBackground = a.getDrawable(attr); + } else if (attr == R.styleable.King_KeyboardView_android_verticalCorrection) { + mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); + } else if (attr == R.styleable.King_KeyboardView_android_keyPreviewLayout) { + previewLayout = a.getResourceId(attr, 0); + } else if (attr == R.styleable.King_KeyboardView_android_keyPreviewOffset) { + mPreviewOffset = a.getDimensionPixelOffset(attr, 0); + } else if (attr == R.styleable.King_KeyboardView_android_keyPreviewHeight) { + mPreviewHeight = a.getDimensionPixelSize(attr, 80); + } else if (attr == R.styleable.King_KeyboardView_android_keyTextSize) { + mKeyTextSize = a.getDimensionPixelSize(attr, 18); + } else if (attr == R.styleable.King_KeyboardView_android_keyTextColor) { + mKeyTextColor = a.getColor(attr, 0xFF000000); + } else if (attr == R.styleable.King_KeyboardView_android_labelTextSize) { + mLabelTextSize = a.getDimensionPixelSize(attr, 14); + } else if (attr == R.styleable.King_KeyboardView_android_popupLayout) { + mPopupLayout = a.getResourceId(attr, 0); + } else if (attr == R.styleable.King_KeyboardView_android_shadowColor) { + mShadowColor = a.getColor(attr, 0); + } else if (attr == R.styleable.King_KeyboardView_android_shadowRadius) { + mShadowRadius = a.getFloat(attr, 0f); + } + } + + mPreviewPopup = new PopupWindow(context); + if (previewLayout != 0) { + mPreviewText = (TextView) inflate.inflate(previewLayout, null); + mPreviewTextSizeLarge = (int) mPreviewText.getTextSize(); + mPreviewPopup.setContentView(mPreviewText); + mPreviewPopup.setBackgroundDrawable(null); + } else { + mShowPreview = false; + } + + mPreviewPopup.setTouchable(false); + + mPopupKeyboard = new PopupWindow(context); + mPopupKeyboard.setBackgroundDrawable(null); + //mPopupKeyboard.setClippingEnabled(false); + + mPopupParent = this; + //mPredicting = true; + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTextSize(keyTextSize); + mPaint.setTextAlign(Align.CENTER); + mPaint.setAlpha(255); + + mPadding = new Rect(0, 0, 0, 0); + mMiniKeyboardCache = new HashMap(); + mKeyBackground.getPadding(mPadding); + + mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density); +// mDisambiguateSwipe = getResources().getBoolean( +// R.bool.config_swipeDisambiguation); + + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + resetMultiTap(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + initGestureDetector(); + if (mHandler == null) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_PREVIEW: + showKey(msg.arg1); + break; + case MSG_REMOVE_PREVIEW: + mPreviewText.setVisibility(INVISIBLE); + break; + case MSG_REPEAT: + if (repeatKey()) { + Message repeat = Message.obtain(this, MSG_REPEAT); + sendMessageDelayed(repeat, REPEAT_INTERVAL); + } + break; + case MSG_LONGPRESS: + openPopupIfRequired((MotionEvent) msg.obj); + break; + } + } + }; + } + } + + private void initGestureDetector() { + if (mGestureDetector == null) { + mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent me1, MotionEvent me2, + float velocityX, float velocityY) { + if (mPossiblePoly) return false; + final float absX = Math.abs(velocityX); + final float absY = Math.abs(velocityY); + float deltaX = me2.getX() - me1.getX(); + float deltaY = me2.getY() - me1.getY(); + int travelX = getWidth() / 2; // Half the keyboard width + int travelY = getHeight() / 2; // Half the keyboard height + mSwipeTracker.computeCurrentVelocity(1000); + final float endingVelocityX = mSwipeTracker.getXVelocity(); + final float endingVelocityY = mSwipeTracker.getYVelocity(); + boolean sendDownKey = false; + if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { + if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) { + sendDownKey = true; + } else { + swipeRight(); + return true; + } + } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { + if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) { + sendDownKey = true; + } else { + swipeLeft(); + return true; + } + } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { + if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) { + sendDownKey = true; + } else { + swipeUp(); + return true; + } + } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { + if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) { + sendDownKey = true; + } else { + swipeDown(); + return true; + } + } + + if (sendDownKey) { + detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime()); + } + return false; + } + }); + + mGestureDetector.setIsLongpressEnabled(false); + } + } + + public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { + mKeyboardActionListener = listener; + } + + /** + * Returns the {@link OnKeyboardActionListener} object. + * @return the listener attached to this keyboard + */ + protected OnKeyboardActionListener getOnKeyboardActionListener() { + return mKeyboardActionListener; + } + + /** + * Attaches a keyboard to this view. The keyboard can be switched at any time and the + * view will re-layout itself to accommodate the keyboard. + * @see Keyboard + * @see #getKeyboard() + * @param keyboard the keyboard to display in this view + */ + public void setKeyboard(Keyboard keyboard) { + if (mKeyboard != null) { + showPreview(NOT_A_KEY); + } + // Remove any pending messages + removeMessages(); + mKeyboard = keyboard; + List keys = mKeyboard.getKeys(); + mKeys = keys.toArray(new Key[keys.size()]); + requestLayout(); + // Hint to reallocate the buffer if the size changed + mKeyboardChanged = true; + invalidateAllKeys(); + computeProximityThreshold(keyboard); + mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views + // Switching to a different keyboard should abort any pending keys so that the key up + // doesn't get delivered to the old or new keyboard + mAbortKey = true; // Until the next ACTION_DOWN + } + + /** + * Returns the current keyboard being displayed by this view. + * @return the currently attached keyboard + * @see #setKeyboard(Keyboard) + */ + public Keyboard getKeyboard() { + return mKeyboard; + } + + /** + * Sets the state of the shift key of the keyboard, if any. + * @param shifted whether or not to enable the state of the shift key + * @return true if the shift key state changed, false if there was no change + * @see KeyboardView#isShifted() + */ + public boolean setShifted(boolean shifted) { + if (mKeyboard != null) { + if (mKeyboard.setShifted(shifted)) { + // The whole keyboard probably needs to be redrawn + invalidateAllKeys(); + return true; + } + } + return false; + } + + /** + * Returns the state of the shift key of the keyboard, if any. + * @return true if the shift is in a pressed state, false otherwise. If there is + * no shift key on the keyboard or there is no keyboard attached, it returns false. + * @see KeyboardView#setShifted(boolean) + */ + public boolean isShifted() { + if (mKeyboard != null) { + return mKeyboard.isShifted(); + } + return false; + } + + /** + * Enables or disables the key feedback popup. This is a popup that shows a magnified + * version of the depressed key. By default the preview is enabled. + * @param previewEnabled whether or not to enable the key feedback popup + * @see #isPreviewEnabled() + */ + public void setPreviewEnabled(boolean previewEnabled) { + mShowPreview = previewEnabled; + } + + /** + * Returns the enabled state of the key feedback popup. + * @return whether or not the key feedback popup is enabled + * @see #setPreviewEnabled(boolean) + */ + public boolean isPreviewEnabled() { + return mShowPreview; + } + + public void setVerticalCorrection(int verticalOffset) { + + } + public void setPopupParent(View v) { + mPopupParent = v; + } + + public void setPopupOffset(int x, int y) { + mMiniKeyboardOffsetX = x; + mMiniKeyboardOffsetY = y; + if (mPreviewPopup.isShowing()) { + mPreviewPopup.dismiss(); + } + } + + /** + * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key + * codes for adjacent keys. When disabled, only the primary key code will be + * reported. + * @param enabled whether or not the proximity correction is enabled + */ + public void setProximityCorrectionEnabled(boolean enabled) { + mProximityCorrectOn = enabled; + } + + /** + * Returns true if proximity correction is enabled. + */ + public boolean isProximityCorrectionEnabled() { + return mProximityCorrectOn; + } + + /** + * Popup keyboard close button clicked. + * @hide + */ + public void onClick(View v) { + dismissPopupKeyboard(); + } + + private CharSequence adjustCase(CharSequence label) { + if (mKeyboard.isShifted() && label != null && label.length() < 3 + && Character.isLowerCase(label.charAt(0))) { + label = label.toString().toUpperCase(); + } + return label; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Round up a little + if (mKeyboard == null) { + setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); + } else { + int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); + if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { + width = MeasureSpec.getSize(widthMeasureSpec); + } + setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); + } + } + + /** + * Compute the average distance between adjacent keys (horizontally and vertically) + * and square it to get the proximity threshold. We use a square here and in computing + * the touch distance from a key's center to avoid taking a square root. + * @param keyboard + */ + private void computeProximityThreshold(Keyboard keyboard) { + if (keyboard == null) return; + final Key[] keys = mKeys; + if (keys == null) return; + int length = keys.length; + int dimensionSum = 0; + for (int i = 0; i < length; i++) { + Key key = keys[i]; + dimensionSum += Math.min(key.width, key.height) + key.gap; + } + if (dimensionSum < 0 || length == 0) return; + mProximityThreshold = (int) (dimensionSum * 1.4f / length); + mProximityThreshold *= mProximityThreshold; // Square it + } + + @Override + public void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (mKeyboard != null) { + mKeyboard.resize(w, h); + } + // Release the buffer, if any and it will be reallocated on the next draw + mBuffer = null; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mDrawPending || mBuffer == null || mKeyboardChanged) { + onBufferDraw(); + } + canvas.drawBitmap(mBuffer, 0, 0, null); + } + + private void onBufferDraw() { + if (mBuffer == null || mKeyboardChanged) { + if (mBuffer == null || mKeyboardChanged && + (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { + // Make sure our bitmap is at least 1x1 + final int width = Math.max(1, getWidth()); + final int height = Math.max(1, getHeight()); + mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBuffer); + } + invalidateAllKeys(); + mKeyboardChanged = false; + } + + if (mKeyboard == null) return; + + mCanvas.save(); + final Canvas canvas = mCanvas; + canvas.clipRect(mDirtyRect); + + final Paint paint = mPaint; + final Drawable keyBackground = mKeyBackground; + final Rect clipRegion = mClipRegion; + final Rect padding = mPadding; + final int kbdPaddingLeft = getPaddingLeft(); + final int kbdPaddingTop = getPaddingTop(); + final Key[] keys = mKeys; + final Key invalidKey = mInvalidatedKey; + + paint.setColor(mKeyTextColor); + boolean drawSingleKey = false; + if (invalidKey != null && canvas.getClipBounds(clipRegion)) { + // Is clipRegion completely contained within the invalidated key? + if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && + invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && + invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && + invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { + drawSingleKey = true; + } + } + canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); + final int keyCount = keys.length; + for (int i = 0; i < keyCount; i++) { + final Key key = keys[i]; + if (drawSingleKey && invalidKey != key) { + continue; + } + int[] drawableState = key.getCurrentDrawableState(); + keyBackground.setState(drawableState); + + // Switch the character to uppercase if shift is pressed + String label = key.label == null? null : adjustCase(key.label).toString(); + + final Rect bounds = keyBackground.getBounds(); + if (key.width != bounds.right || + key.height != bounds.bottom) { + keyBackground.setBounds(0, 0, key.width, key.height); + } + canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop); + keyBackground.draw(canvas); + + if (label != null) { + // For characters, use large font. For labels like "Done", use small font. + if (label.length() > 1 && key.codes.length < 2) { + paint.setTextSize(mLabelTextSize); + paint.setTypeface(Typeface.DEFAULT_BOLD); + } else { + paint.setTextSize(mKeyTextSize); + paint.setTypeface(Typeface.DEFAULT); + } + // Draw a drop shadow for the text + paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + // Draw the text + canvas.drawText(label, + (key.width - padding.left - padding.right) / 2 + + padding.left, + (key.height - padding.top - padding.bottom) / 2 + + (paint.getTextSize() - paint.descent()) / 2 + padding.top, + paint); + // Turn off drop shadow + paint.setShadowLayer(0, 0, 0, 0); + } else if (key.icon != null) { + final int drawableX = (key.width - padding.left - padding.right + - key.icon.getIntrinsicWidth()) / 2 + padding.left; + final int drawableY = (key.height - padding.top - padding.bottom + - key.icon.getIntrinsicHeight()) / 2 + padding.top; + canvas.translate(drawableX, drawableY); + key.icon.setBounds(0, 0, + key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight()); + key.icon.draw(canvas); + canvas.translate(-drawableX, -drawableY); + } + canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); + } + mInvalidatedKey = null; + // Overlay a dark rectangle to dim the keyboard + if (mMiniKeyboardOnScreen) { +// paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); + canvas.drawRect(0, 0, getWidth(), getHeight(), paint); + } + + if (DEBUG && mShowTouchPoints) { + paint.setAlpha(128); + paint.setColor(0xFFFF0000); + canvas.drawCircle(mStartX, mStartY, 3, paint); + canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint); + paint.setColor(0xFF0000FF); + canvas.drawCircle(mLastX, mLastY, 3, paint); + paint.setColor(0xFF00FF00); + canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint); + } + mCanvas.restore(); + mDrawPending = false; + mDirtyRect.setEmpty(); + } + + private int getKeyIndices(int x, int y, int[] allKeys) { + final Key[] keys = mKeys; + int primaryIndex = NOT_A_KEY; + int closestKey = NOT_A_KEY; + int closestKeyDist = mProximityThreshold + 1; + java.util.Arrays.fill(mDistances, Integer.MAX_VALUE); + int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y); + final int keyCount = nearestKeyIndices.length; + for (int i = 0; i < keyCount; i++) { + final Key key = keys[nearestKeyIndices[i]]; + int dist = 0; + boolean isInside = key.isInside(x,y); + if (isInside) { + primaryIndex = nearestKeyIndices[i]; + } + + if (((mProximityCorrectOn + && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) + || isInside) + && key.codes[0] > 32) { + // Find insertion point + final int nCodes = key.codes.length; + if (dist < closestKeyDist) { + closestKeyDist = dist; + closestKey = nearestKeyIndices[i]; + } + + if (allKeys == null) continue; + + for (int j = 0; j < mDistances.length; j++) { + if (mDistances[j] > dist) { + // Make space for nCodes codes + System.arraycopy(mDistances, j, mDistances, j + nCodes, + mDistances.length - j - nCodes); + System.arraycopy(allKeys, j, allKeys, j + nCodes, + allKeys.length - j - nCodes); + for (int c = 0; c < nCodes; c++) { + allKeys[j + c] = key.codes[c]; + mDistances[j + c] = dist; + } + break; + } + } + } + } + if (primaryIndex == NOT_A_KEY) { + primaryIndex = closestKey; + } + return primaryIndex; + } + + private void detectAndSendKey(int index, int x, int y, long eventTime) { + if (index != NOT_A_KEY && index < mKeys.length) { + final Key key = mKeys[index]; + if (key.text != null) { + mKeyboardActionListener.onText(key.text); + mKeyboardActionListener.onRelease(NOT_A_KEY); + } else { + int code = key.codes[0]; + //TextEntryState.keyPressedAt(key, x, y); + int[] codes = new int[MAX_NEARBY_KEYS]; + Arrays.fill(codes, NOT_A_KEY); + getKeyIndices(x, y, codes); + // Multi-tap + if (mInMultiTap) { + if (mTapCount != -1) { + mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE); + } else { + mTapCount = 0; + } + code = key.codes[mTapCount]; + } + mKeyboardActionListener.onKey(code, codes); + mKeyboardActionListener.onRelease(code); + } + mLastSentIndex = index; + mLastTapTime = eventTime; + } + } + + /** + * Handle multi-tap keys by producing the key label for the current multi-tap state. + */ + private CharSequence getPreviewText(Key key) { + if (mInMultiTap) { + // Multi-tap + mPreviewLabel.setLength(0); + mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); + return adjustCase(mPreviewLabel); + } else { + return adjustCase(key.label); + } + } + + private void showPreview(int keyIndex) { + int oldKeyIndex = mCurrentKeyIndex; + final PopupWindow previewPopup = mPreviewPopup; + + mCurrentKeyIndex = keyIndex; + // Release the old key and press the new key + final Key[] keys = mKeys; + if (oldKeyIndex != mCurrentKeyIndex) { + if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) { + Key oldKey = keys[oldKeyIndex]; + oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY); + invalidateKey(oldKeyIndex); + final int keyCode = oldKey.codes[0]; + } + if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) { + Key newKey = keys[mCurrentKeyIndex]; + newKey.onPressed(); + invalidateKey(mCurrentKeyIndex); + final int keyCode = newKey.codes[0]; + } + } + // If key changed and preview is on ... + if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) { + mHandler.removeMessages(MSG_SHOW_PREVIEW); + if (previewPopup.isShowing()) { + if (keyIndex == NOT_A_KEY) { + mHandler.sendMessageDelayed(mHandler + .obtainMessage(MSG_REMOVE_PREVIEW), + DELAY_AFTER_PREVIEW); + } + } + if (keyIndex != NOT_A_KEY) { + if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) { + // Show right away, if it's already visible and finger is moving around + showKey(keyIndex); + } else { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), + DELAY_BEFORE_PREVIEW); + } + } + } + } + + private void showKey(final int keyIndex) { + final PopupWindow previewPopup = mPreviewPopup; + final Key[] keys = mKeys; + if (keyIndex < 0 || keyIndex >= mKeys.length) return; + Key key = keys[keyIndex]; + if (key.icon != null) { + mPreviewText.setCompoundDrawables(null, null, null, + key.iconPreview != null ? key.iconPreview : key.icon); + mPreviewText.setText(null); + } else { + mPreviewText.setCompoundDrawables(null, null, null, null); + mPreviewText.setText(getPreviewText(key)); + if (key.label!=null && key.label.length() > 1 && key.codes.length < 2) { + mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize); + mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); + } else { + mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); + mPreviewText.setTypeface(Typeface.DEFAULT); + } + } + mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width + + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); + final int popupHeight = mPreviewHeight; + LayoutParams lp = mPreviewText.getLayoutParams(); + if (lp != null) { + lp.width = popupWidth; + lp.height = popupHeight; + } + if (!mPreviewCentered) { + mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft(); + mPopupPreviewY = key.y - popupHeight + mPreviewOffset; + } else { + // TODO: Fix this if centering is brought back + mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2; + mPopupPreviewY = - mPreviewText.getMeasuredHeight(); + } + mHandler.removeMessages(MSG_REMOVE_PREVIEW); + getLocationInWindow(mCoordinates); + mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero + mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero + + // Set the preview background state + mPreviewText.getBackground().setState( + key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); + mPopupPreviewX += mCoordinates[0]; + mPopupPreviewY += mCoordinates[1]; + + // If the popup cannot be shown above the key, put it on the side + getLocationOnScreen(mCoordinates); + if (mPopupPreviewY + mCoordinates[1] < 0) { + // If the key you're pressing is on the left side of the keyboard, show the popup on + // the right, offset by enough to see at least one key to the left/right. + if (key.x + key.width <= getWidth() / 2) { + mPopupPreviewX += (int) (key.width * 2.5); + } else { + mPopupPreviewX -= (int) (key.width * 2.5); + } + mPopupPreviewY += popupHeight; + } + + if (previewPopup.isShowing()) { + previewPopup.update(mPopupPreviewX, mPopupPreviewY, + popupWidth, popupHeight); + } else { + previewPopup.setWidth(popupWidth); + previewPopup.setHeight(popupHeight); + previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, + mPopupPreviewX, mPopupPreviewY); + } + mPreviewText.setVisibility(VISIBLE); + } + + + /** + * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient + * because the keyboard renders the keys to an off-screen buffer and an invalidate() only + * draws the cached buffer. + * @see #invalidateKey(int) + */ + public void invalidateAllKeys() { + mDirtyRect.union(0, 0, getWidth(), getHeight()); + mDrawPending = true; + invalidate(); + } + + /** + * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only + * one key is changing it's content. Any changes that affect the position or size of the key + * may not be honored. + * @param keyIndex the index of the key in the attached {@link Keyboard}. + * @see #invalidateAllKeys + */ + public void invalidateKey(int keyIndex) { + if (mKeys == null) return; + if (keyIndex < 0 || keyIndex >= mKeys.length) { + return; + } + final Key key = mKeys[keyIndex]; + mInvalidatedKey = key; + mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(), + key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); + onBufferDraw(); + invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(), + key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); + } + + private boolean openPopupIfRequired(MotionEvent me) { + // Check if we have a popup layout specified first. + if (mPopupLayout == 0) { + return false; + } + if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) { + return false; + } + + Key popupKey = mKeys[mCurrentKey]; + boolean result = onLongPress(popupKey); + if (result) { + mAbortKey = true; + showPreview(NOT_A_KEY); + } + return result; + } + + /** + * Called when a key is long pressed. By default this will open any popup keyboard associated + * with this key through the attributes popupLayout and popupCharacters. + * @param popupKey the key that was long pressed + * @return true if the long press is handled, false otherwise. Subclasses should call the + * method on the base class if the subclass doesn't wish to handle the call. + */ + protected boolean onLongPress(Key popupKey) { + int popupKeyboardId = popupKey.popupResId; + + if (popupKeyboardId != 0) { + mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey); + if (mMiniKeyboardContainer == null) { + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null); + mMiniKeyboard = mMiniKeyboardContainer.findViewById( + R.id.keyboardView); +// View closeButton = mMiniKeyboardContainer.findViewById( +// R.id.closeButton); +// if (closeButton != null) closeButton.setOnClickListener(this); + mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() { + public void onKey(int primaryCode, int[] keyCodes) { + mKeyboardActionListener.onKey(primaryCode, keyCodes); + dismissPopupKeyboard(); + } + + public void onText(CharSequence text) { + mKeyboardActionListener.onText(text); + dismissPopupKeyboard(); + } + + public void swipeLeft() { } + public void swipeRight() { } + public void swipeUp() { } + public void swipeDown() { } + public void onPress(int primaryCode) { + mKeyboardActionListener.onPress(primaryCode); + } + public void onRelease(int primaryCode) { + mKeyboardActionListener.onRelease(primaryCode); + } + }); + //mInputView.setSuggest(mSuggest); + Keyboard keyboard; + if (popupKey.popupCharacters != null) { + keyboard = new Keyboard(getContext(), popupKeyboardId, + popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight()); + } else { + keyboard = new Keyboard(getContext(), popupKeyboardId); + } + mMiniKeyboard.setKeyboard(keyboard); + mMiniKeyboard.setPopupParent(this); + mMiniKeyboardContainer.measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); + + mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer); + } else { + mMiniKeyboard = mMiniKeyboardContainer.findViewById( + R.id.keyboardView); + } + getLocationInWindow(mCoordinates); + mPopupX = popupKey.x + getPaddingLeft(); + mPopupY = popupKey.y + getPaddingTop(); + mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth(); + mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight(); + final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0]; + final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1]; + mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y); + mMiniKeyboard.setShifted(isShifted()); + mPopupKeyboard.setContentView(mMiniKeyboardContainer); + mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth()); + mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight()); + mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y); + mMiniKeyboardOnScreen = true; + //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me)); + invalidateAllKeys(); + return true; + } + return false; + } + +// @Override +// public boolean onHoverEvent(MotionEvent event) { +// if (AccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) { +// final int action = event.getAction(); +// switch (action) { +// case MotionEvent.ACTION_HOVER_ENTER: { +// event.setAction(MotionEvent.ACTION_DOWN); +// } break; +// case MotionEvent.ACTION_HOVER_MOVE: { +// event.setAction(MotionEvent.ACTION_MOVE); +// } break; +// case MotionEvent.ACTION_HOVER_EXIT: { +// event.setAction(MotionEvent.ACTION_UP); +// } break; +// } +// return onTouchEvent(event); +// } +// return true; +// } + + @Override + public boolean onTouchEvent(MotionEvent me) { + // Convert multi-pointer up/down events to single up/down events to + // deal with the typical multi-pointer behavior of two-thumb typing + final int pointerCount = me.getPointerCount(); + final int action = me.getAction(); + boolean result = false; + final long now = me.getEventTime(); + + if (pointerCount != mOldPointerCount) { + if (pointerCount == 1) { + // Send a down event for the latest pointer + MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, + me.getX(), me.getY(), me.getMetaState()); + result = onModifiedTouchEvent(down, false); + down.recycle(); + // If it's an up action, then deliver the up as well. + if (action == MotionEvent.ACTION_UP) { + result = onModifiedTouchEvent(me, true); + } + } else { + // Send an up event for the last pointer + MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, + mOldPointerX, mOldPointerY, me.getMetaState()); + result = onModifiedTouchEvent(up, true); + up.recycle(); + } + } else { + if (pointerCount == 1) { + result = onModifiedTouchEvent(me, false); + mOldPointerX = me.getX(); + mOldPointerY = me.getY(); + } else { + // Don't do anything when 2 pointers are down and moving. + result = true; + } + } + mOldPointerCount = pointerCount; + + return result; + } + + private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) { + int touchX = (int) me.getX() - getPaddingLeft(); + int touchY = (int) me.getY() - getPaddingTop(); + if (touchY >= -mVerticalCorrection) + touchY += mVerticalCorrection; + final int action = me.getAction(); + final long eventTime = me.getEventTime(); + int keyIndex = getKeyIndices(touchX, touchY, null); + mPossiblePoly = possiblePoly; + + // Track the last few movements to look for spurious swipes. + if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear(); + mSwipeTracker.addMovement(me); + + // Ignore all motion events until a DOWN. + if (mAbortKey + && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) { + return true; + } + + if (mGestureDetector.onTouchEvent(me)) { + showPreview(NOT_A_KEY); + mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); + return true; + } + + // Needs to be called after the gesture detector gets a turn, as it may have + // displayed the mini keyboard + if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) { + return true; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + mAbortKey = false; + mStartX = touchX; + mStartY = touchY; + mLastCodeX = touchX; + mLastCodeY = touchY; + mLastKeyTime = 0; + mCurrentKeyTime = 0; + mLastKey = NOT_A_KEY; + mCurrentKey = keyIndex; + mDownKey = keyIndex; + mDownTime = me.getEventTime(); + mLastMoveTime = mDownTime; + checkMultiTap(eventTime, keyIndex); + mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? + mKeys[keyIndex].codes[0] : 0); + if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) { + mRepeatKeyIndex = mCurrentKey; + Message msg = mHandler.obtainMessage(MSG_REPEAT); + mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY); + repeatKey(); + // Delivering the key could have caused an abort + if (mAbortKey) { + mRepeatKeyIndex = NOT_A_KEY; + break; + } + } + if (mCurrentKey != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } + showPreview(keyIndex); + break; + + case MotionEvent.ACTION_MOVE: + boolean continueLongPress = false; + if (keyIndex != NOT_A_KEY) { + if (mCurrentKey == NOT_A_KEY) { + mCurrentKey = keyIndex; + mCurrentKeyTime = eventTime - mDownTime; + } else { + if (keyIndex == mCurrentKey) { + mCurrentKeyTime += eventTime - mLastMoveTime; + continueLongPress = true; + } else if (mRepeatKeyIndex == NOT_A_KEY) { + resetMultiTap(); + mLastKey = mCurrentKey; + mLastCodeX = mLastX; + mLastCodeY = mLastY; + mLastKeyTime = + mCurrentKeyTime + eventTime - mLastMoveTime; + mCurrentKey = keyIndex; + mCurrentKeyTime = 0; + } + } + } + if (!continueLongPress) { + // Cancel old longpress + mHandler.removeMessages(MSG_LONGPRESS); + // Start new longpress if key has changed + if (keyIndex != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } + } + showPreview(mCurrentKey); + mLastMoveTime = eventTime; + break; + + case MotionEvent.ACTION_UP: + removeMessages(); + if (keyIndex == mCurrentKey) { + mCurrentKeyTime += eventTime - mLastMoveTime; + } else { + resetMultiTap(); + mLastKey = mCurrentKey; + mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime; + mCurrentKey = keyIndex; + mCurrentKeyTime = 0; + } + if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME + && mLastKey != NOT_A_KEY) { + mCurrentKey = mLastKey; + touchX = mLastCodeX; + touchY = mLastCodeY; + } + showPreview(NOT_A_KEY); + Arrays.fill(mKeyIndices, NOT_A_KEY); + // If we're not on a repeating key (which sends on a DOWN event) + if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) { + detectAndSendKey(mCurrentKey, touchX, touchY, eventTime); + } + invalidateKey(keyIndex); + mRepeatKeyIndex = NOT_A_KEY; + break; + case MotionEvent.ACTION_CANCEL: + removeMessages(); + dismissPopupKeyboard(); + mAbortKey = true; + showPreview(NOT_A_KEY); + invalidateKey(mCurrentKey); + break; + } + mLastX = touchX; + mLastY = touchY; + return true; + } + + private boolean repeatKey() { + Key key = mKeys[mRepeatKeyIndex]; + detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime); + return true; + } + + protected void swipeRight() { + mKeyboardActionListener.swipeRight(); + } + + protected void swipeLeft() { + mKeyboardActionListener.swipeLeft(); + } + + protected void swipeUp() { + mKeyboardActionListener.swipeUp(); + } + + protected void swipeDown() { + mKeyboardActionListener.swipeDown(); + } + + public void closing() { + if (mPreviewPopup.isShowing()) { + mPreviewPopup.dismiss(); + } + removeMessages(); + + dismissPopupKeyboard(); + mBuffer = null; + mCanvas = null; + mMiniKeyboardCache.clear(); + } + + private void removeMessages() { + if (mHandler != null) { + mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); + mHandler.removeMessages(MSG_SHOW_PREVIEW); + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + closing(); + } + + private void dismissPopupKeyboard() { + if (mPopupKeyboard.isShowing()) { + mPopupKeyboard.dismiss(); + mMiniKeyboardOnScreen = false; + invalidateAllKeys(); + } + } + + public boolean handleBack() { + if (mPopupKeyboard.isShowing()) { + dismissPopupKeyboard(); + return true; + } + return false; + } + + private void resetMultiTap() { + mLastSentIndex = NOT_A_KEY; + mTapCount = 0; + mLastTapTime = -1; + mInMultiTap = false; + } + + private void checkMultiTap(long eventTime, int keyIndex) { + if (keyIndex == NOT_A_KEY) return; + Key key = mKeys[keyIndex]; + if (key.codes.length > 1) { + mInMultiTap = true; + if (eventTime < mLastTapTime + MULTITAP_INTERVAL + && keyIndex == mLastSentIndex) { + mTapCount = (mTapCount + 1) % key.codes.length; + return; + } else { + mTapCount = -1; + return; + } + } + if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) { + resetMultiTap(); + } + } + + private static class SwipeTracker { + + static final int NUM_PAST = 4; + static final int LONGEST_PAST_TIME = 200; + + final float mPastX[] = new float[NUM_PAST]; + final float mPastY[] = new float[NUM_PAST]; + final long mPastTime[] = new long[NUM_PAST]; + + float mYVelocity; + float mXVelocity; + + public void clear() { + mPastTime[0] = 0; + } + + public void addMovement(MotionEvent ev) { + long time = ev.getEventTime(); + final int N = ev.getHistorySize(); + for (int i=0; i= 0) { + final int start = drop+1; + final int count = NUM_PAST-drop-1; + System.arraycopy(pastX, start, pastX, 0, count); + System.arraycopy(pastY, start, pastY, 0, count); + System.arraycopy(pastTime, start, pastTime, 0, count); + i -= (drop+1); + } + pastX[i] = x; + pastY[i] = y; + pastTime[i] = time; + i++; + if (i < NUM_PAST) { + pastTime[i] = 0; + } + } + + public void computeCurrentVelocity(int units) { + computeCurrentVelocity(units, Float.MAX_VALUE); + } + + public void computeCurrentVelocity(int units, float maxVelocity) { + final float[] pastX = mPastX; + final float[] pastY = mPastY; + final long[] pastTime = mPastTime; + + final float oldestX = pastX[0]; + final float oldestY = pastY[0]; + final long oldestTime = pastTime[0]; + float accumX = 0; + float accumY = 0; + int N=0; + while (N < NUM_PAST) { + if (pastTime[N] == 0) { + break; + } + N++; + } + + for (int i=1; i < N; i++) { + final int dur = (int)(pastTime[i] - oldestTime); + if (dur == 0) continue; + float dist = pastX[i] - oldestX; + float vel = (dist/dur) * units; // pixels/frame. + if (accumX == 0) accumX = vel; + else accumX = (accumX + vel) * .5f; + + dist = pastY[i] - oldestY; + vel = (dist/dur) * units; // pixels/frame. + if (accumY == 0) accumY = vel; + else accumY = (accumY + vel) * .5f; + } + mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) + : Math.min(accumX, maxVelocity); + mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) + : Math.min(accumY, maxVelocity); + } + + public float getXVelocity() { + return mXVelocity; + } + + public float getYVelocity() { + return mYVelocity; + } + } +} + diff --git a/keybordlib/src/main/java/com/king/keyboard/KingKeyboard.kt b/keybordlib/src/main/java/com/king/keyboard/KingKeyboard.kt new file mode 100644 index 0000000..d7bd16c --- /dev/null +++ b/keybordlib/src/main/java/com/king/keyboard/KingKeyboard.kt @@ -0,0 +1,1141 @@ +package com.king.keyboard + +import android.app.Activity +import android.content.Context +import android.graphics.drawable.Drawable +import android.media.AudioManager +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.provider.Settings +import android.text.InputType +import android.util.Log +import android.util.SparseArray +import android.view.* +import android.view.animation.Animation +import android.view.animation.TranslateAnimation +import android.widget.EditText +import androidx.annotation.IdRes +import androidx.annotation.XmlRes + + +/** + * @author Jenly + */ +open class KingKeyboard { + + private lateinit var context: Context + + private var isCap = false + + private var isAllCaps = false + + private var keyboardType = KeyboardType.NORMAL + + private val keyboardNormal by lazy { Keyboard(context, R.xml.king_keyboard_normal) } + private val keyboardNormalModeChange by lazy { Keyboard(context, R.xml.king_keyboard_normal_mode_change) } + private val keyboardNormalMore by lazy { Keyboard(context, R.xml.king_keyboard_normal_more_symbol) } + + private val keyboardLetter by lazy { Keyboard(context, R.xml.king_keyboard_letter) } + + private val keyboardLowercaseLetter by lazy { Keyboard(context, R.xml.king_keyboard_lowercase_letter_only) } + + private val keyboardUppercaseLetter by lazy { Keyboard(context, R.xml.king_keyboard_uppercase_letter_only) } + + private val keyboardLetterNumber by lazy { Keyboard(context, R.xml.king_keyboard_letter_number) } + + private val keyboardNumber by lazy { Keyboard(context, R.xml.king_keyboard_number) } + + private val keyboardNumberDecimal by lazy { Keyboard(context, R.xml.king_keyboard_number_decimal) } + + private val keyboardPhone by lazy { Keyboard(context, R.xml.king_keyboard_phone) } + + private val keyboardIDCard by lazy { Keyboard(context, R.xml.king_keyboard_id_card) } + + private val keyboardLicensePlate by lazy { Keyboard(context, R.xml.king_keyboard_license_plate) } + + /** + * LICENSE_PLATE_MODE_CHANGE 与 LICENSE_PLATE_MODE_NUMBER + */ + private val keyboardLicensePlateNumber by lazy { Keyboard(context, R.xml.king_keyboard_license_plate_number) } + + private val keyboardLicensePlateMore by lazy { Keyboard(context, R.xml.king_keyboard_license_plate_more) } + + private val keyboardLicensePlateProvince by lazy { Keyboard(context, R.xml.king_keyboard_license_plate_province) } + + + private var keyboardCustom: Keyboard? = null + private var keyboardCustomModeChange: Keyboard? = null + private var keyboardCustomMore: Keyboard? = null + + private var currentKeyboard: Keyboard + + private lateinit var keyboardViewGroup: View + private var keyboardView: KingKeyboardView? = null + + private var currentEditText: EditText? = null + + /** + * SparseArray存储 EditText,SparseArray 的key为 EditText的 ID,value为 EditText + */ + private val editTextArray by lazy { + SparseArray() + } + + /** + * 键盘类型,SparseArray的key为EditText的ID,value为键盘类型 + */ + private val keyboardTypeArray by lazy { + SparseArray() + } + /** + * 键盘显示动画 + */ + private lateinit var showAnimation: Animation + /** + * 键盘隐藏动画 + */ + private lateinit var hideAnimation: Animation + + private lateinit var onTouchListener: View.OnTouchListener + private lateinit var globalFocusChangeListener: ViewTreeObserver.OnGlobalFocusChangeListener + + private var onKeyboardActionListener: KeyboardView.OnKeyboardActionListener? = null + private var onKeyDoneListener: OnKeyListener? = null + private var onKeyCancelListener: OnKeyListener? = null + private var onKeyExtraListener: OnKeyListener? = null + + private var vibrator: Vibrator? = null + + private var audioManager: AudioManager? = null + + /** + * 是否震动 + */ + private var isVibrationEffect = false + /** + * 是否播放音效 + */ + private var isPlaySoundEffect = false + + companion object{ + + private const val TAG = "KingKeyboard" + + private const val ANIM_DURATION_TIME = 200L + + //------------------------------ 下面是定义的一些公用功能按键值 + /** + * Shift键 -> 一般用来切换键盘大小写字母 + */ + const val KEYCODE_SHIFT = -1 + /** + * 模式改变 -> 切换键盘输入法 + */ + const val KEYCODE_MODE_CHANGE = -2 + /** + * 取消键 -> 关闭输入法 + */ + const val KEYCODE_CANCEL = -3 + /** + * 完成键 -> 长出现在右下角蓝色的完成按钮 + */ + const val KEYCODE_DONE = -4 + /** + * 删除键 -> 删除输入框内容 + */ + const val KEYCODE_DELETE = -5 + /** + * Alt键 -> 预留,暂时未使用 + */ + const val KEYCODE_ALT = -6 + /** + * 空格键 + */ + const val KEYCODE_SPACE = 32 + + /** + * 无作用键 -> 一般用来占位或者禁用按键 + */ + const val KEYCODE_NONE = 0 + + //------------------------------ + + /** + * 键盘按键 -> 返回(返回,适用于切换键盘后界面使用,如:NORMAL_MODE_CHANGE或CUSTOM_MODE_CHANGE键盘) + */ + const val KEYCODE_MODE_BACK = -101 + + /** + * 键盘按键 ->返回(直接返回到最初,直接返回到NORMAL或CUSTOM键盘) + */ + const val KEYCODE_BACK = -102 + + /** + * 键盘按键 ->更多 + */ + const val KEYCODE_MORE = -103 + + //------------------------------ 下面是自定义的一些预留按键值,与共用按键功能一致,但会使用默认的背景按键 + + const val KEYCODE_KING_SHIFT = -201 + const val KEYCODE_KING_MODE_CHANGE = -202 + const val KEYCODE_KING_CANCEL = -203 + const val KEYCODE_KING_DONE = -204 + const val KEYCODE_KING_DELETE = -205 + const val KEYCODE_KING_ALT = -206 + + //------------------------------ 下面是自定义的一些功能按键值,与共用按键功能一致,但会使用默认背景颜色 + + /** + * 键盘按键 -> 返回(返回,适用于切换键盘后界面使用,如:NORMAL_MODE_CHANGE或CUSTOM_MODE_CHANGE键盘) + */ + const val KEYCODE_KING_MODE_BACK = -251 + + /** + * 键盘按键 ->返回(直接返回到最初,直接返回到NORMAL或CUSTOM键盘) + */ + const val KEYCODE_KING_BACK = -252 + + /** + * 键盘按键 ->更多 + */ + const val KEYCODE_KING_MORE = -253 + + /* + 用户也可自定义按键值,primaryCode范围区间为-999 ~ -300时,表示预留可扩展按键值。 + 其中-399~-300区间为功能型按键,使用Special背景色,-999~-400自定义按键为默认背景色 + */ + + } + + /** + * 构造 + * @param activity 上下文 + * @param keyboardParentView 键盘的父布局容器 -> 一般在界面底部,用来容纳键盘布局 + * + */ + constructor(activity: Activity, keyboardParentView: ViewGroup): + this(activity, + activity.window.decorView.findViewById(android.R.id.content).getChildAt(0) as ViewGroup, + keyboardParentView + ) + + /** + * 构造 + * @param context 上下文 + * @param rootView 界面的根布局 -> 也可以是当前界面包含所有的EditText的公共父布局 + * @param keyboardParentView 键盘的父布局容器 -> 一般在界面底部,用来容纳键盘布局 + */ + constructor(context: Context, rootView: ViewGroup,keyboardParentView: ViewGroup): + this(context, + rootView, + keyboardParentView, + LayoutInflater.from(context).inflate(R.layout.king_keyboard_container, null), + R.id.keyboardView + ) + /** + * 构造 + * @param context 上下文 + * @param rootView 界面的根布局 -> 也可以是当前界面包含所有的EditText的公共父布局 + * @param keyboardParentView 键盘的父布局容器 -> 一般在界面底部,用来容纳键盘布局 + * @param keyboardContainer 键盘的容器 + * @param keyboardViewId KingKeyboard视图控件的ID + */ + constructor(context: Context, rootView: ViewGroup,keyboardParentView: ViewGroup,keyboardContainer: View,@IdRes keyboardViewId: Int) { + this.context = context + currentKeyboard = keyboardNormal + intKeyboard(context) + intKeyboardView(rootView,keyboardParentView,keyboardContainer,keyboardViewId) + + } + + open fun intKeyboard(context: Context){ + + } + + /** + * 初始化KeyboardView + */ + private fun intKeyboardView(rootView: ViewGroup,keyboardParentView: ViewGroup,keyboardContainer: View,@IdRes keyboardViewId: Int){ + //初始化键盘相关 + keyboardViewGroup = keyboardContainer + keyboardView = keyboardViewGroup.findViewById(keyboardViewId) + + keyboardView?.let { + it.keyboard = currentKeyboard + it.isEnabled = true + it.isPreviewEnabled = false + it.onKeyboardActionListener = object: KeyboardView.OnKeyboardActionListener{ + + override fun swipeRight() { + onKeyboardActionListener?.swipeRight() + } + + override fun onPress(primaryCode: Int) { + onKeyboardActionListener?.onPress(primaryCode) + } + + override fun onRelease(primaryCode: Int) { + onKeyboardActionListener?.onRelease(primaryCode) + } + + override fun swipeLeft() { + onKeyboardActionListener?.swipeLeft() + } + + override fun swipeUp() { + onKeyboardActionListener?.swipeUp() + } + + override fun swipeDown() { + onKeyboardActionListener?.swipeDown() + } + + override fun onKey(primaryCode: Int, keyCodes: IntArray?) { + if(primaryCode != 0){ + playSoundEffect() + sendVibrationEffect() + } + //根据不同的按键值去处理 + when(primaryCode){ + KEYCODE_SHIFT -> keyShift() + KEYCODE_MODE_CHANGE -> keyModeChange() + KEYCODE_CANCEL -> keyCancel(primaryCode) + KEYCODE_DONE -> keyDone(primaryCode) + KEYCODE_DELETE -> keyDelete() + KEYCODE_ALT -> keyAlt() + KEYCODE_MODE_BACK -> keyBack(false) + KEYCODE_BACK -> keyBack(true) + KEYCODE_MORE -> keyMore() + //预留值,具有相同的作用 + KEYCODE_KING_SHIFT -> keyShift() + KEYCODE_KING_MODE_CHANGE -> keyModeChange() + KEYCODE_KING_CANCEL -> keyCancel(primaryCode) + KEYCODE_KING_DONE -> keyDone(primaryCode) + KEYCODE_KING_DELETE -> keyDelete() + KEYCODE_KING_ALT -> keyAlt() + KEYCODE_KING_MODE_BACK -> keyBack(false) + KEYCODE_KING_BACK -> keyBack(true) + KEYCODE_KING_MORE -> keyMore() + //预留的自定义可扩展按键 + in -999..-300 -> keyExtra(primaryCode) + //直接输入按键值 + in 32..Int.MAX_VALUE -> keyInput(primaryCode) + //无效的按键值,打印相关日志 + else -> Log.d(TAG,"primaryCode:$primaryCode") + } + onKeyboardActionListener?.onKey(primaryCode,keyCodes) + + } + + override fun onText(text: CharSequence?) { + onKeyboardActionListener?.onText(text) + } + } + + isCap = it.isCap() + isAllCaps = it.isAllCaps() + + keyboardViewGroup.isVisible = false + } + + //初始化动画 + showAnimation = TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, + 0.0f, Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0.0f) + showAnimation.duration = ANIM_DURATION_TIME + + hideAnimation = TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, + 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 1.0f) + hideAnimation.duration = ANIM_DURATION_TIME + + hideAnimation.setAnimationListener(object : Animation.AnimationListener{ + override fun onAnimationRepeat(animation: Animation?) { + + } + + override fun onAnimationEnd(animation: Animation?) { + if(keyboardViewGroup.isVisible){ + keyboardViewGroup.isVisible = false + } + } + + override fun onAnimationStart(animation: Animation?) { + + } + + }) + + onTouchListener = View.OnTouchListener { v, event -> + if(event.action == MotionEvent.ACTION_UP){ + viewFocus(v) + } + + false + } + + globalFocusChangeListener = ViewTreeObserver.OnGlobalFocusChangeListener {oldFocus, newFocus -> + if(newFocus is EditText){ + if(editTextArray.containsKey(newFocus.id)){//newFocus使用的是KingKeyboard + viewFocus(newFocus) + }else{//没有使用KingKeyboard,可能使用的是系统输入法,则隐藏KingKeyboard + hide() + } + } + } + + //将键盘布局添加到父布局 + keyboardParentView.addView(keyboardViewGroup) + + rootView.viewTreeObserver.addOnGlobalFocusChangeListener(globalFocusChangeListener) + } + + /** + * 自定义键盘Custom,键盘类型为{@link KeyboardType#CUSTOM} + * + * 当默认已有的键盘类型满足不了您的需求时,可通过此方法来自定义键盘。 + * + * 与之相关的方法有{@code setKeyboardCustomModeChange(Keyboard)}和{@code setKeyboardCustomMore(Keyboard)} + * + * @param keyboard 键盘 + */ + fun setKeyboardCustom(keyboard: Keyboard){ + this.keyboardCustom = keyboard + } + + /** + * 自定义键盘CustomModeChange,键盘类型为{@link KeyboardType#CUSTOM_MODE_CHANGE} + * + * 当需要自定义键盘的按键太多,一个自定义键盘布局满足不了你的需求时,即Custom不够用时,你可以通过 + * 自定义CustomModeChange来扩展,通过键盘切换,来满足您的需求。 + * + * 与之相关的方法有{@code setKeyboardCustom(Keyboard)}和{@code setKeyboardCustomMore(Keyboard)} + * + * @param keyboard 键盘 + */ + fun setKeyboardCustomModeChange(keyboard: Keyboard){ + this.keyboardCustomModeChange = keyboard + } + + /** + * 自定义键盘CustomMore,键盘类型为{@link KeyboardType#CUSTOM_MORE} + * + * 当需要自定义键盘的按键太多,两个自定义键盘布局满足不了你的需求时,即Custom加上CustomModeChange还不够用时, + * 你可以通过自定义CustomModeChange来扩展,通过键盘切换,来满足您的需求。 + * + * 与之相关的方法有{@code setKeyboardCustom(Keyboard)}和{@code setKeyboardCustomModeChange(Keyboard)} + * + * @param keyboard 键盘 + */ + fun setKeyboardCustomMore(keyboard: Keyboard){ + this.keyboardCustomMore = keyboard + } + + /** + * 自定义键盘Custom,键盘类型为{@link KeyboardType#CUSTOM} + * + * 当默认已有的键盘类型满足不了您的需求时,可通过此方法来自定义键盘。 + * + * 与之相关的方法有{@code setKeyboardCustomModeChange(Int)}和{@code setKeyboardCustomMore(Int)} + * + * @param xmlLayoutResId 键盘布局的资源文件,其中包含键盘布局和键值码等相关信息 + */ + fun setKeyboardCustom(@XmlRes xmlLayoutResId: Int){ + this.keyboardCustom = Keyboard(context,xmlLayoutResId) + } + + /** + * 自定义键盘CustomModeChange,键盘类型为{@link KeyboardType#CUSTOM_MODE_CHANGE} + * + * 当需要自定义键盘的按键太多,一个自定义键盘布局满足不了你的需求时,即Custom不够用时,你可以通过 + * 自定义CustomModeChange来扩展,通过键盘切换,来满足您的需求。 + * + * 与之相关的方法有{@code setKeyboardCustom(Int)}和{@code setKeyboardCustomMore(Int)} + * + * @param xmlLayoutResId 键盘布局的资源文件,其中包含键盘布局和键值码等相关信息 + */ + fun setKeyboardCustomModeChange(@XmlRes xmlLayoutResId: Int){ + this.keyboardCustomModeChange = Keyboard(context,xmlLayoutResId) + } + + /** + * 自定义键盘CustomMore,键盘类型为{@link KeyboardType#CUSTOM_MORE} + * + * 当需要自定义键盘的按键太多,两个自定义键盘布局满足不了你的需求时,即Custom加上CustomModeChange还不够用时, + * 你可以通过自定义CustomModeChange来扩展,通过键盘切换,来满足您的需求。 + * + * 与之相关的方法有{@code setKeyboardCustom(Int)}和{@code setKeyboardCustomModeChange(Int)} + * + * @param xmlLayoutResId 键盘布局的资源文件,其中包含键盘布局和键值码等相关信息 + */ + fun setKeyboardCustomMore(@XmlRes xmlLayoutResId: Int){ + this.keyboardCustomMore = Keyboard(context,xmlLayoutResId) + } + + /** + * 获取当前键盘输入法类型 + * return 返回当前键盘输入法类型 + */ + fun getKeyboardType(): Int{ + return keyboardType + } + + private fun disableShowSoftInput(editText: EditText){ + try { + val method = EditText::class.java.getMethod("setShowSoftInputOnFocus", Boolean::class.java) + method.isAccessible = true + method.invoke(editText, false) + } catch (e: Exception) { + editText.inputType = InputType.TYPE_NULL + } + } + + /** + * 执行当获View获取焦点时的一些逻辑,如:显示键盘 + */ + private fun viewFocus(v: View){ + if(v is EditText){ + v.hideSystemInputMethod() + disableShowSoftInput(v) + if(v.hasFocus()){ + currentEditText = v + keyboardType = keyboardTypeArray[v.id]!! + switchKeyboard() + show() + } + } + } + + + /** + * 注册 + * @param editText 要注册的EditText + * @param keyboardType 键盘输入法类型 + */ + fun register(editText: EditText,keyboardType: Int) { + editTextArray[editText.id] = editText + keyboardTypeArray[editText.id] = keyboardType + editText.setOnTouchListener(onTouchListener) + } + + fun onResume(){ + currentEditText?.let { + if(it.hasFocus()){ + it.postDelayed({ it.hideSystemInputMethod() },100) + } + } + isPlaySoundEffect = querySoundEffectsEnabled() + } + + fun onDestroy(){ + currentEditText?.let { + it.clearAnimation() + currentEditText = null + } + editTextArray.clear() + keyboardTypeArray.clear() + } + + /** + * 键盘输入法是否显示 + */ + fun isShow(): Boolean{ + return keyboardViewGroup.isVisible + } + + /** + * 显示键盘输入法 + */ + private fun show(){ + if(!keyboardViewGroup.isVisible){ + keyboardViewGroup.apply { + isVisible = true + clearAnimation() + startAnimation(showAnimation) + } + } + } + + /** + * 隐藏键盘输入法 + */ + open fun hide(){ + if(keyboardViewGroup.isVisible){ + keyboardViewGroup.apply { + clearAnimation() + startAnimation(hideAnimation) + } + } + } + + /** + * 设置背景 + */ + fun setBackground(drawable: Drawable?){ + drawable?.let { + keyboardViewGroup.background = drawable + } + } + + /** + * 设置背景 + */ + fun setBackgroundResource(drawableId: Int){ + keyboardViewGroup.setBackgroundResource(drawableId) + } + + /** + * 对外提供获取KingKeyboardView + */ + fun getKeyboardView(): KingKeyboardView? { + return keyboardView + } + + /** + * 对外提供获取KingKeyboardView的配置 + */ + fun getKeyboardViewConfig(): KingKeyboardView.Config?{ + return keyboardView?.getConfig() + } + + /** + * 对外提供设置KingKeyboardView的配置 + */ + fun setKeyboardViewConfig(config: KingKeyboardView.Config){ + keyboardView?.setConfig(config) + } + + //---------------------------------- + + /** + * 是否开启音效 -> 由系统设置决定,暂不对外提供 + */ + private fun isSoundEffectsEnabled(): Boolean{ + return isPlaySoundEffect + } + + /** + * 设置是否开启音效 -> 由系统设置决定,暂不对外提供 + */ + private fun setSoundEffectEnabled(soundEffectEnabled: Boolean){ + this.isPlaySoundEffect = soundEffectEnabled + setSoundEffectsEnabled(isPlaySoundEffect) + } + + /** + * 是否开启震动 + */ + fun isVibrationEffectEnabled(): Boolean{ + return isVibrationEffect + } + + /** + * 设置是否开启震动 + */ + fun setVibrationEffectEnabled(vibrationEffectEnabled: Boolean){ + this.isVibrationEffect = vibrationEffectEnabled + } + + /** + * 对外提供监听键盘相关动作 + */ + fun setOnKeyboardActionListener(listener: KeyboardView.OnKeyboardActionListener?){ + this.onKeyboardActionListener = listener + } + + /** + * 对外提供监听“完成”按键 + */ + fun setOnKeyDoneListener(listener: OnKeyListener?){ + this.onKeyDoneListener = listener + } + + /** + * 对外提供监听“关闭键盘”按键 + */ + fun setOnKeyCancelListener(listener: OnKeyListener?){ + this.onKeyCancelListener = listener + } + + /** + * 对外提供监听扩展自定义的按键 + */ + fun setOnKeyExtraListener(listener: OnKeyListener?){ + this.onKeyExtraListener = listener + } + + /** + * 监听“完成”按键接口 + */ + interface OnKeyListener{ + /** + * 点击触发按键时,触发此回调方法 + * @param primaryCode 为原始code值,即Key的code + */ + fun onKey(editText: View?,primaryCode: Int) + } + + //---------------------------------- + + /** + * 切换键盘输入法 + */ + private fun switchKeyboard(){ + when(keyboardType){ + KeyboardType.NORMAL -> { + currentKeyboard = keyboardNormal + } + KeyboardType.NORMAL_MODE_CHANGE -> { + currentKeyboard = keyboardNormalModeChange + } + KeyboardType.NORMAL_MORE -> { + currentKeyboard = keyboardNormalMore + } + KeyboardType.LETTER -> { + currentKeyboard = keyboardLetter + } + KeyboardType.LOWERCASE_LETTER_ONLY -> { + currentKeyboard = keyboardLowercaseLetter + } + KeyboardType.UPPERCASE_LETTER_ONLY -> { + currentKeyboard = keyboardUppercaseLetter + } + KeyboardType.LETTER_NUMBER -> { + currentKeyboard = keyboardLetterNumber + } + KeyboardType.NUMBER -> { + currentKeyboard = keyboardNumber + } + KeyboardType.NUMBER_DECIMAL -> { + currentKeyboard = keyboardNumberDecimal + } + KeyboardType.PHONE -> { + currentKeyboard = keyboardPhone + } + KeyboardType.ID_CARD -> { + currentKeyboard = keyboardIDCard + } + KeyboardType.LICENSE_PLATE -> { + currentKeyboard = keyboardLicensePlate + } + KeyboardType.LICENSE_PLATE_MODE_CHANGE -> { + currentKeyboard = keyboardLicensePlateNumber + } + KeyboardType.LICENSE_PLATE_MORE -> { + currentKeyboard = keyboardLicensePlateMore + } + KeyboardType.LICENSE_PLATE_PROVINCE -> { + currentKeyboard = keyboardLicensePlateProvince + } + KeyboardType.LICENSE_PLATE_NUMBER -> { + currentKeyboard = keyboardLicensePlateNumber + } + KeyboardType.CUSTOM -> {//当自定义了键盘,但没有自定义相关布局时,使用默认键盘keyboardNormal + currentKeyboard = keyboardCustom ?: keyboardNormal + } + KeyboardType.CUSTOM_MODE_CHANGE -> {//当自定义了键盘,但没有自定义相关布局时,使用默认键盘keyboardNormalModeChange + currentKeyboard = keyboardCustomModeChange ?: keyboardNormalModeChange + } + KeyboardType.CUSTOM_MORE -> {//当自定义了键盘,但没有自定义相关布局时,使用默认键盘keyboardNormalMore + currentKeyboard = keyboardCustomMore ?: keyboardNormalMore + } + + } + + keyboardView?.run { + keyboard = currentKeyboard + } + + } + + /** + * 模式改变,切换键盘 + */ + private fun keyModeChange(){ + when(keyboardType){ + KeyboardType.NORMAL -> { + keyboardType = KeyboardType.NORMAL_MODE_CHANGE + } + KeyboardType.LICENSE_PLATE -> { + keyboardType = KeyboardType.LICENSE_PLATE_MODE_CHANGE + } + KeyboardType.LICENSE_PLATE_MORE -> { + keyboardType = KeyboardType.LICENSE_PLATE_MODE_CHANGE + } + KeyboardType.LICENSE_PLATE_PROVINCE -> { + keyboardType = KeyboardType.LICENSE_PLATE_NUMBER + } + KeyboardType.CUSTOM -> { + keyboardType = KeyboardType.CUSTOM_MODE_CHANGE + } + KeyboardType.CUSTOM_MORE -> { + keyboardType = KeyboardType.CUSTOM_MODE_CHANGE + } + } + + switchKeyboard() + } + + /** + * 取消,关闭键盘 + */ + private fun keyCancel(primaryCode: Int){ + hide() + onKeyCancelListener?.onKey(currentEditText,primaryCode) + } + + /** + * 完成 + */ + private fun keyDone(primaryCode: Int){ + hide() + onKeyDoneListener?.onKey(currentEditText,primaryCode) + } + + /** + * Alt键,暂时未用到 + */ + private fun keyAlt(){ + + } + + /** + * 返回 + */ + private fun keyBack(isBack: Boolean){ + when(keyboardType){ + KeyboardType.NORMAL_MODE_CHANGE -> { + keyboardType = KeyboardType.NORMAL + } + KeyboardType.NORMAL_MORE -> { + keyboardType = if(isBack) KeyboardType.NORMAL else KeyboardType.NORMAL_MODE_CHANGE + } + KeyboardType.CUSTOM_MODE_CHANGE -> { + keyboardType = KeyboardType.CUSTOM + } + KeyboardType.CUSTOM_MORE -> { + keyboardType = if(isBack) KeyboardType.CUSTOM else KeyboardType.CUSTOM_MODE_CHANGE + } + KeyboardType.LICENSE_PLATE -> { + keyboardType = KeyboardType.LICENSE_PLATE_NUMBER + } + KeyboardType.LICENSE_PLATE_MODE_CHANGE -> { + keyboardType = KeyboardType.LICENSE_PLATE + } + KeyboardType.LICENSE_PLATE_MORE -> { + keyboardType = if(isBack) KeyboardType.LICENSE_PLATE else KeyboardType.LICENSE_PLATE_MODE_CHANGE + } + KeyboardType.LICENSE_PLATE_PROVINCE -> { + keyboardType = KeyboardType.LICENSE_PLATE_NUMBER + } + KeyboardType.LICENSE_PLATE_NUMBER -> { + keyboardType = KeyboardType.LICENSE_PLATE_PROVINCE + } + } + + switchKeyboard() + } + + /** + * 更多 + */ + private fun keyMore(){ + + when(keyboardType){ + KeyboardType.NORMAL -> { + keyboardType = KeyboardType.NORMAL_MORE + } + KeyboardType.NORMAL_MODE_CHANGE -> { + keyboardType = KeyboardType.NORMAL_MORE + } + KeyboardType.LICENSE_PLATE -> { + keyboardType = KeyboardType.LICENSE_PLATE_MORE + } + KeyboardType.LICENSE_PLATE_MODE_CHANGE -> { + keyboardType = KeyboardType.LICENSE_PLATE_MORE + } + KeyboardType.CUSTOM -> { + keyboardType = KeyboardType.CUSTOM_MORE + } + KeyboardType.CUSTOM_MODE_CHANGE -> { + keyboardType = KeyboardType.CUSTOM_MORE + } + + } + + switchKeyboard() + } + + /** + * 输入 + */ + private fun keyInput(primaryCode: Int){ + currentEditText?.let { + val start = it.selectionStart + val end = it.selectionEnd + + it.text?.replace(start,end, primaryCode.toChar().toString()) + if(isCap && !isAllCaps){//如果当前是大写键盘,并且并且没有锁定,则自动变换成小写键盘 + isCap = false + isAllCaps = false + toLowerCaseKey(currentKeyboard) + + keyboardView?.run { + setCap(isCap) + setAllCaps(isAllCaps) + keyboard = currentKeyboard + } + + } + + } + } + + private fun querySoundEffectsEnabled(): Boolean { + return Settings.System.getInt(context.contentResolver, + Settings.System.SOUND_EFFECTS_ENABLED, 0) != 0 + } + + private fun setSoundEffectsEnabled(enabled: Boolean){ + Settings.System.putInt(context.contentResolver, + Settings.System.SOUND_EFFECTS_ENABLED, if(enabled) 1 else 0) + } + + /** + * 播放音效 + */ + private fun playSoundEffect(effectType: Int = AudioManager.FX_KEYPRESS_STANDARD){ + if(isPlaySoundEffect){ + try{ + if(audioManager == null){ + audioManager = (context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager) + } + audioManager?.playSoundEffect(effectType) + }catch (e: Exception){ + Log.w(TAG,e) + } + } + } + + /** + * 震动 + */ + private fun sendVibrationEffect(){ + if(isVibrationEffect){ + try { + if(vibrator == null){ + vibrator = (context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator) + } + //震动 + vibrator?.let { + if (Build.VERSION.SDK_INT >= 26) { + it.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) + }else{ + it.vibrate(16L) + } + } + }catch (e: Exception){ + Log.w(TAG,e) + } + } + } + + + /** + * 按下 + */ + private fun keyDown(keycode: Int,action: Int = KeyEvent.ACTION_DOWN){ + currentEditText?.let { + it.onKeyDown(keycode, KeyEvent(action,keycode)) + } + } + + /** + * 触发删除 + */ + private fun keyDelete(){ + keyDown(KeyEvent.KEYCODE_DEL) + } + + /** + * 触发自定义可扩展按键 + */ + private fun keyExtra(primaryCode: Int){ + Log.d(TAG,"primaryCode:$primaryCode") + onKeyExtraListener?.onKey(currentEditText,primaryCode) + } + + /** + * 触发Shift,切换大小字母键盘 + */ + private fun keyShift(){ + + //将键盘进行大小写键盘切换 + + if(isAllCaps){//上次状态为大写锁定时,转换为小写 + toLowerCaseKey(currentKeyboard) + }else{//反之上次状态即为小写时,转换为大写 + toUpperCaseKey(currentKeyboard) + } + + when { + isAllCaps -> {//上次状态为锁定时,此次状态将改变为小写,将变量状态改变 + isAllCaps = false + isCap = false + } + isCap -> {//上次状态为非锁定,此次状态改变为锁定 + isAllCaps = true + } + else -> {//上次状态为小写(默认),此次状态改变为大写 + isCap = true + isAllCaps = false + } + } + + keyboardView?.let { + it.setCap(isCap) + it.setAllCaps(isAllCaps) + it.keyboard = currentKeyboard + } + + + + } + + /** + * 转换为大写 + */ + private fun toUpperCaseKey(keyboard: Keyboard){ + keyboard.run { + for(key in keys){ + if(key.label?.length == 1){// 一个字符 + var c = key.label.toString()[0] + if(c.isLowerCase()){ //是小写字母 + //转换为大写 + val letter = c.toUpperCase() + key.label = letter.toString() + key.codes[0] = letter.toInt() + } + + } + } + } + + } + + /** + * 转换为小写 + */ + private fun toLowerCaseKey(keyboard: Keyboard){ + keyboard.run { + for(key in keys){ + if(key.label?.length == 1){// 一个字符 + var c = key.label.toString()[0] + if(c.isUpperCase()){ //是大写字母 + //转换为小写 + val letter = c.toLowerCase() + key.label = letter.toString() + key.codes[0] = letter.toInt() + } + + } + } + } + + } + + + /** + * 键盘类型 + */ + object KeyboardType{ + /** + * 默认键盘 - 字母带符号 + */ + const val NORMAL = 0x00000001 + /** + * 默认键盘 - 切换键盘 + */ + internal const val NORMAL_MODE_CHANGE = 0x00000002 + /** + * 默认键盘 - 更多 + */ + internal const val NORMAL_MORE = 0x00000003 + + /** + * 字母键盘 + */ + const val LETTER = 0x00000011 + + /** + * 仅小写字母键盘 + */ + const val LOWERCASE_LETTER_ONLY = 0x00000101 + /** + * 仅大写字母键盘 + */ + const val UPPERCASE_LETTER_ONLY = 0x00000102 + + /** + * 字母+数字键盘 + */ + const val LETTER_NUMBER = 0x00000201 + + /** + * 数字键盘 + */ + const val NUMBER = 0x00000301 + /** + * 浮点数键盘(数字加“.”符号) + */ + const val NUMBER_DECIMAL = 0x00000302 + + /** + * 电话拨号键盘(数字加“-”符号) + */ + const val PHONE = 0x00000303 + + /** + * 身份证键盘 + */ + const val ID_CARD = 0x00000304 + /** + * 车牌键盘 - 车牌 -> 归属地 + 切换车牌号 + */ + const val LICENSE_PLATE = 0x00000401 + + /** + * 车牌键盘- 切换 -> 车牌号 + */ + internal const val LICENSE_PLATE_MODE_CHANGE = 0x00000402 + + /** + * 车牌键盘 - 更多 + */ + internal const val LICENSE_PLATE_MORE = 0x00000403 + + /** + * 车牌键盘 - 车牌 相对于 LICENSE_PLATE 少了更多相关的 + */ + const val LICENSE_PLATE_PROVINCE = 0x00000404 + + /** + * 车牌键盘 - 车牌号 + */ + internal const val LICENSE_PLATE_NUMBER = 0x00000405 + + /** + * 预留自定义键盘类型 + */ + const val CUSTOM = 0x00001001 + /** + * 预留自定义键盘类型 - 键盘模式切换 + */ + const val CUSTOM_MODE_CHANGE = 0x00001002 + /** + * 预留自定义键盘类型 - 更多 + */ + const val CUSTOM_MORE = 0x00001003 + + } +} \ No newline at end of file diff --git a/keybordlib/src/main/java/com/king/keyboard/KingKeyboardUtil.kt b/keybordlib/src/main/java/com/king/keyboard/KingKeyboardUtil.kt new file mode 100644 index 0000000..dcbe572 --- /dev/null +++ b/keybordlib/src/main/java/com/king/keyboard/KingKeyboardUtil.kt @@ -0,0 +1,55 @@ +package com.king.keyboard + +import android.content.Context +import android.util.SparseArray +import android.view.View +import android.view.inputmethod.InputMethodManager +import androidx.annotation.RequiresApi + +/** + * @author Jenly + */ + +/** + * 判断是否是小写字母 + */ +fun Char.isLowerCase(c: Char): Boolean{ + return c.toInt() in 97..122 +} + +/** + * 判断是否是大写字母 + */ +fun Char.isUpperCase(c: Char): Boolean{ + return c.toInt() in 65..90 +} + +/** + * 显示系统输入法 + */ +fun View.showSystemInputMethod(){ + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(this,InputMethodManager.SHOW_IMPLICIT) +} + +/** + * 隐藏系统输入法 + */ +fun View.hideSystemInputMethod(){ + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(windowToken,0) +} + +var View.isVisible: Boolean + get() = visibility == View.VISIBLE + set(value) { + visibility = if (value) View.VISIBLE else View.GONE + } + +/** Returns true if the collection contains [key]. */ +@RequiresApi(16) +fun SparseArray.containsKey(key: Int) = indexOfKey(key) >= 0 + +/** Allows the use of the index operator for storing values in the collection. */ +@RequiresApi(16) +operator fun SparseArray.set(key: Int, value: T) = put(key, value) diff --git a/keybordlib/src/main/java/com/king/keyboard/KingKeyboardView.kt b/keybordlib/src/main/java/com/king/keyboard/KingKeyboardView.kt new file mode 100644 index 0000000..1e6b55f --- /dev/null +++ b/keybordlib/src/main/java/com/king/keyboard/KingKeyboardView.kt @@ -0,0 +1,316 @@ +package com.king.keyboard + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat + +/** + * @author Jenly + */ +open class KingKeyboardView : KeyboardView { + + + private var isCap = false + + private var isAllCaps = false + + private lateinit var config: Config + + private val paint by lazy { Paint() } + + companion object { + const val iconRatio = 0.5f + + } + + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes){ + init(context, attrs) + } + + + private fun init(context: Context, attrs: AttributeSet?) { + config = Config(context) + + var a = context.obtainStyledAttributes(attrs, R.styleable.KingKeyboardView) + + a.indexCount.let { + config.run { + for (i in 0 until it) { + when (val attr = a.getIndex(i)) { + R.styleable.KingKeyboardView_kkbDeleteDrawable -> deleteDrawable = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbCapitalDrawable -> capitalDrawable = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbCapitalLockDrawable -> capitalLockDrawable = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbCancelDrawable -> cancelDrawable = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbCancelDrawable -> spaceDrawable = a.getDrawable(attr) + R.styleable.KingKeyboardView_android_labelTextSize -> labelTextSize = a.getDimensionPixelSize(attr, labelTextSize) + R.styleable.KingKeyboardView_android_keyTextSize -> keyTextSize = a.getDimensionPixelSize(attr, keyTextSize) + R.styleable.KingKeyboardView_android_keyTextColor -> keyTextColor = a.getColor(attr, keyTextColor) + R.styleable.KingKeyboardView_kkbKeyIconColor -> keyIconColor = a.getColor(attr, ContextCompat.getColor(context, R.color.king_keyboard_key_icon_color)) + R.styleable.KingKeyboardView_kkbKeySpecialTextColor -> keySpecialTextColor = a.getColor(attr, keySpecialTextColor) + R.styleable.KingKeyboardView_kkbKeyDoneTextColor -> keyDoneTextColor = a.getColor(attr, keyDoneTextColor) + R.styleable.KingKeyboardView_kkbKeyNoneTextColor -> keyNoneTextColor = a.getColor(attr, keyNoneTextColor) + R.styleable.KingKeyboardView_android_keyBackground -> keyBackground = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbSpecialKeyBackground -> specialKeyBackground = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbDoneKeyBackground -> doneKeyBackground = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbNoneKeyBackground -> noneKeyBackground = a.getDrawable(attr) + R.styleable.KingKeyboardView_kkbKeyDoneTextSize -> keyDoneTextSize = a.getDimensionPixelSize(attr, keyDoneTextSize) + R.styleable.KingKeyboardView_kkbKeyDoneText -> keyDoneText = a.getString(attr) + } + } + } + a.recycle() + } + + paint.textAlign = Paint.Align.CENTER + paint.isAntiAlias = true + + } + + fun getConfig(): Config { + return config + } + + fun setConfig(config: Config) { + this.config = config + invalidate() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawKeyboard(canvas, keyboard?.keys) + } + + /** + * 绘制键盘 + */ + private fun drawKeyboard(canvas: Canvas,keys: List?){ + keys?.let { + for (key in it) { + drawKey(canvas, key) + } + } + } + + /** + * 绘制键盘按键 + */ + private fun drawKey(canvas: Canvas, key: Keyboard.Key) { + when (key.codes[0]) { + KingKeyboard.KEYCODE_SHIFT -> drawShiftKey(canvas, key) + KingKeyboard.KEYCODE_MODE_CHANGE -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor) + KingKeyboard.KEYCODE_CANCEL -> drawCancelKey(canvas, key) + KingKeyboard.KEYCODE_DONE -> drawDoneKey(canvas, key) + KingKeyboard.KEYCODE_DELETE -> drawDeleteKey(canvas, key) + KingKeyboard.KEYCODE_ALT -> drawAltKey(canvas, key) + KingKeyboard.KEYCODE_SPACE -> drawKey(canvas, key, config.keyBackground, config.keyTextColor, config.spaceDrawable) + KingKeyboard.KEYCODE_NONE -> drawNoneKey(canvas, key) + KingKeyboard.KEYCODE_MODE_BACK -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor) + KingKeyboard.KEYCODE_BACK -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor) + KingKeyboard.KEYCODE_MORE -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor) + in -399..-300 -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor) +// else -> drawKey(canvas,key,keyBackground) + } + } + + /** + * 绘制Cancel键,常见于关闭键盘键 + */ + private fun drawCancelKey(canvas: Canvas, key: Keyboard.Key) { + drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor, config.cancelDrawable) + } + + /** + * 绘制Done键,常见于右下角蓝色的“确定”按键 + */ + private fun drawDoneKey(canvas: Canvas, key: Keyboard.Key) { + config.keyDoneText?.let { + key.label = it + } + drawKey(canvas, key, config.doneKeyBackground, config.keyDoneTextColor, null, true) + } + + /** + * 绘制Delete键 + */ + private fun drawNoneKey(canvas: Canvas, key: Keyboard.Key) { + drawKey(canvas, key, config.noneKeyBackground, config.keyNoneTextColor) + } + + /** + * 绘制Alt键 + */ + private fun drawAltKey(canvas: Canvas, key: Keyboard.Key) { + drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor) + } + + /** + * 绘制Delete键 + */ + private fun drawDeleteKey(canvas: Canvas, key: Keyboard.Key) { + drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor, config.deleteDrawable) + } + + /** + * 绘制Shift键 + */ + private fun drawShiftKey(canvas: Canvas, key: Keyboard.Key) { + when { + isAllCaps -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor, config.capitalLockDrawable) + isCap -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor, config.capitalDrawable) + else -> drawKey(canvas, key, config.specialKeyBackground, config.keySpecialTextColor, config.lowerDrawable) + } + } + + /** + * 绘制键盘按键 + */ + private fun drawKey(canvas: Canvas, key: Keyboard.Key, keyBackground: Drawable?, textColor: Int, iconDrawable: Drawable? = key.icon, isDone: Boolean = false) { + //绘制按键背景 + keyBackground?.run { + if (key.codes[0] != 0) { + state = key.currentDrawableState + } + + setBounds( + key.x.plus(paddingLeft), + key.y.plus(paddingTop), + key.x.plus(paddingLeft).plus(key.width), + key.y.plus(paddingTop).plus(key.height) + ) + draw(canvas) + } + + //绘制键盘图标 + iconDrawable?.run { + + val drawable = DrawableCompat.wrap(this) + config.keyIconColor?.takeIf { it != 0 }?.let { + drawable.setTint(it) + } + + key.icon = drawable + + var iconWidth = key.icon.intrinsicWidth.toFloat() + var iconHeight = key.icon.intrinsicHeight.toFloat() + + val widthRatio = iconWidth.div(key.width.toFloat()) + val heightRatio = iconHeight.div(key.height.toFloat()) + + if (widthRatio <= heightRatio) {//当图标的宽占比小于等于高占比时,以高度比例为基准并控制在iconRatio比例范围内,进行同比例缩放 + + val ratio = heightRatio.coerceAtMost(iconRatio) + iconWidth = iconWidth.div(heightRatio).times(ratio) + iconHeight = iconHeight.div(heightRatio).times(ratio) + + } else {//反之,则以宽度比例为基准并控制在iconRatio比例范围内,进行同比例缩放 + + val ratio = widthRatio.coerceAtMost(iconRatio) + iconWidth = iconWidth.div(widthRatio).times(ratio) + iconHeight = iconHeight.div(widthRatio).times(ratio) + + } + + val left = key.x.plus(paddingLeft).plus(key.width.minus(iconWidth).div(2f)).toInt() + val top = key.y.plus(paddingTop).plus(key.height.minus(iconHeight).div(2f)).toInt() + val right = left.plus(iconWidth).toInt() + val bottom = top.plus(iconHeight).toInt() + key.icon.setBounds(left, top, right, bottom) + key.icon.draw(canvas) + + } ?: key.label?.let { + //绘制键盘文字 + if (isDone) { + paint.textSize = config.keyDoneTextSize.toFloat() + } else if (it.length > 1 && key.codes.size < 2) {// 键盘key内容多个字符 + paint.textSize = config.labelTextSize.toFloat() + } else { + paint.textSize = config.keyTextSize.toFloat() + } + paint.color = textColor + paint.typeface = Typeface.DEFAULT + + canvas.drawText( + it.toString(), + key.x.plus(paddingLeft).plus(key.width.div(2f)), + key.y.plus(paddingTop).plus(key.height.div(2.0f)).plus( + paint.textSize.minus(paint.descent()).div(2.0f) + ), + paint + ) + + } + + } + + + fun setCap(isCap: Boolean) { + this.isCap = isCap + } + + fun isCap(): Boolean { + return isCap + } + + fun setAllCaps(isAllCaps: Boolean) { + this.isAllCaps = isAllCaps + } + + fun isAllCaps(): Boolean { + return isAllCaps + } + + /** + * Config为KingKeyboard的配置类,方便统一管理配置信息 + */ + open class Config(context: Context) { + + var deleteDrawable = context.getDrawable(R.drawable.king_keyboard_key_delete) + var lowerDrawable = context.getDrawable(R.drawable.king_keyboard_key_lower) + var capitalDrawable = context.getDrawable(R.drawable.king_keyboard_key_cap) + var capitalLockDrawable = context.getDrawable(R.drawable.king_keyboard_key_all_caps) + var cancelDrawable = context.getDrawable(R.drawable.king_keyboard_key_cancel) + var spaceDrawable = context.getDrawable(R.drawable.king_keyboard_key_space) + + var labelTextSize = context.resources.getDimensionPixelSize(R.dimen.king_keyboard_label_text_size) + + var keyTextSize = context.resources.getDimensionPixelSize(R.dimen.king_keyboard_text_size) + + var keyTextColor = ContextCompat.getColor(context, R.color.king_keyboard_key_text_color) + + var keyIconColor: Int? = null + + var keySpecialTextColor = ContextCompat.getColor(context, R.color.king_keyboard_key_special_text_color) + + var keyDoneTextColor = ContextCompat.getColor(context, R.color.king_keyboard_key_done_text_color) + + var keyNoneTextColor = ContextCompat.getColor(context, R.color.king_keyboard_key_none_text_color) + + var keyBackground = context.getDrawable(R.drawable.king_keyboard_key_bg) + + var specialKeyBackground = context.getDrawable(R.drawable.king_keyboard_special_key_bg) + + var doneKeyBackground = context.getDrawable(R.drawable.king_keyboard_done_key_bg) + var noneKeyBackground = context.getDrawable(R.drawable.king_keyboard_none_key_bg) + + var keyDoneTextSize = context.resources.getDimensionPixelSize(R.dimen.king_keyboard_done_text_size) + + var keyDoneText: CharSequence? = context.getString(R.string.king_keyboard_key_done_text) + + } + + +} \ No newline at end of file diff --git a/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_all_caps.png b/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_all_caps.png new file mode 100644 index 0000000000000000000000000000000000000000..e53dd00e3553f171f99c2cc12e1fbc07e3baa65e GIT binary patch literal 1673 zcmaJ?eM}Q)7(bvmOM-5ExB%*og6P1u_tCFws8U*xszoS>7~<^p?w}pCSK9+Bb6ATq z%Wz>DKQ=cdQ-%_TOhr+uqUZ{lk(o@VZqvr7Gs6giQ^|1d3e@?B^OAet_j&L6JwRbsLzc!OPMc%X?? z@K%Vmkk+cAjHawA8AwmS5TG0_&#ZxJ`p<&1*IGMxy)Y}IOC8n)Oc6UC-X=)kQ)SO5vlW`A7c+IB`q{YT@q z+V=dlR!XR&>~y(}VCzww5CF5ed$S|IBFjd;%w}SXf|sdjqTEbb7>!!NW4{PU6Dfz$ zbP<%UQi+vf84PDgl`>d`NnuQeq9QROK?62khgGXHGh|8#L6LNf#VVC3ELN$Zj0_o~ z&d7wN0j$PiXK)Ka1^k*=zmr(_jaa$LM&S%?%ctqGKnLU)X@<5NX)CBw`7164=dCbV zNV?MgzQ05h5vwVise&RiZL}F2&#~O}8V)3hU}7J2>g=Y-7k@z8J@}X=y*?3=inTes(qM-&t2z<1Bz?L67ixa=z~Q3h&h6oKwVbSJ!azkK9?bs=@gk@^<^^i|H4% zxu^HZ-_rWr=QN|&Dt>f7aK>$Vl=_RR;PLt`h9J+t$>bb&?9i`=oePF~&JX8YRR(7P zTreon$!P#0(*94lJ2dO!b4hk^+u1KJ+!^g?yZvOUhqK5>pYI!?4|$OC!^t&~UHYn9 h$xS;OKYcd08@S3bCm#ImWSsx!pvlNqx25Y{{{r#lW!C@z literal 0 HcmV?d00001 diff --git a/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_cancel.png b/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f18e10f7056d70d07efa19dea1dc26abf06cbd GIT binary patch literal 1390 zcmV-!1(EuRP)KIlWSeNm}JR8Xlu;7=7=toZY0qlnZ# z)GDad`hykxf3OuRCx-v)SZsx_jqo@7eh| z=bJO|_#nkKh9{dp4UrXZ7(5 z5nUMpuv-YR->S2hh+awnSOjW*3rH#N6GFUcQQb{M2O?BwB~w#VQ|*0yeSd~^q?83A zglDxms+77j;zjQv;$5N4_x&3@&-*rf?&#=Pk;~=&vh)}vqV-Ge0r`BsJ(J1IxOXa* zGP%O<_P~WgVLc+Q4rR9D6&Xq?H#4&^&&+(fR4NVFef0G7bO<5Vg|e}+G1cGSKjZR7 zUMLhcBI3p7w-DkS5ve5yU=Og|e@iJBKL#v!qZ1FX2rP*QNGVq_bAAp_81_7GFcK8k z_`bh3lm$U>$_|P~?z--^zVEN7)H&;Uo*g8iSS*^eiDK9XB9^t*Bas>3z|5D2G9knc zM10r^l=Z&`UDv%I0N$tufSJE1qU$0Ny%!NpViT5ol~QjitG##-Ddya)6>(HPN(g|nIlAWB+^H8|1e92DH|vZ5vIT0DxFOtRmuh zt@V$QShSj%XHJQjd9YL}4c8R|VwlWWhe+~(BoBz>0gmGYh#1}oX}yo5j^hj<;;au0 zu`bF64#v#G=7z|Nh`6_bdNgckWwO@ zcs&5*Vpb&9;h6ak0QioXA2NrmO?@41K4cRB^PyC49rJBJo6YWF=C=9tg@_ZQqoW6g zhK9!HQ@@@%v3WqTSlpIMr9KCM^#3UQx9|Hmd!Bc!9>eC_HZ}l8A=h=y!OBMfkoqqL z%?(aO+^&@RY`zB6TQ61sMp?&kZU=y)R`xk<1OHp@(OSP-uTizMj~ze-!mMjI6#x$s zQPodywHQ@P-G%_DKzI}p4_1udMMMp`q1rG26$sCmtNB{%eYMQmV%BXafWy3+q+uCC;)DsqO&Foq;sk}~P z!ucoiqW_x&;ixM|KrBA=zzDu z&to1wAa77uP!HeY7#Za5;fA6>;0?7(5kOIxI$CBoh`gk_Xd4Q?=s1C+2z`M8FK{@F zcB7dfoF==Oa9B))#jMvGjAV{Jreiv;v%sLo&9qZb7FYn+4tRFDAjg3_3&BDgV_cUM zP#KBkV|E2#calp#luJ1jo>67RE6a7U4k)dWRXI>2`w@pDT5%(?WQ`zkaxjn;EzyIB zXV42N@Cj>H)Q!}$z8BY{rxx&M

#`nV@Wkrlo{ndsRrk%`rkuozI>U>5Iu9ReO`UxdfU)&Ax^+M7@Xj>Hcc%U& zzWnGX>otjO>EE$m%{vi!1%AH0wfX7Zt3&VII!GA5w7zi2eX8%R*LuU3eAQ)-bUjAg z&MwYHwSAu^A9=22^Jsf*s^<9qw=;*O)tbVbC7Gsm+3;@h8MpKiKO@!FF@o5CA9t<@ z|IB8)QxX>TyON4V+dOY>Z%pj*z^B_sR|evjj}IS%SG1p2GT@}*cj2b?9)FfKgQ?#W zj%+)XNWjv^b+3k-Qpqz+(o+ol`GGq`TIRLfIQXXzDt^09oe6roD-(8Ssi!ZWO)DAI zY+l+s)|cDl>FW;X`JXJ^l{7At4^W|t$E_Sy1SKJwC&KZ2pp<<0zl65*M)=UZp-fH1 zwa+Slxw~%R-~a#s literal 0 HcmV?d00001 diff --git a/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_delete.png b/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..8cbc449b82e733647bd64e174c10a09bd3a3c14e GIT binary patch literal 2096 zcmV-02+#M4P)wt?QHB_ce0)!}pNZ5Ct87dVu&?;1vlA0({N%$%P1SKFSstQyU{Q%KQ z6;Oo4cWG&$38f*3(h~8}f>MMK2xe~M*oaCIgf>vQDnYg)==pB?*up6A^F0H4nb@Kkb%Xbce_ z27n)Ht^ZSNe$xq1O05QfM*v{?R0g){IT3vX0Go|5+g+VaB|r$V+4H=+0l;?^5mAhY zLqv2SC*Vi1w;>{XTt_V(MD&m`W-|c9>GP%$pp?1;0KV@it`gB!BHB4NHul`m(9pr0 zz}sR`2vGolO^A3cA~G=(gNPnXNErXEDFjFOpdY(CP5<+~#^Sq}4 zfVW~ETx*Pj?d@G21i{OQxWsY$pBrP`lsAiBcaE==`Xd0WwI2K(09H?y zJ@)nWt?TIM*kM(goNuhxTJOwi#FPXS3WXlu_n)f){(HulyRs_EFwd7#Zb!uRHVNf& z`75~@M!kvkWy|% zM9v}7@lLf`J>3p!Qc6y@e}{w_GOnrECQvz5)Qp*d!29 zD^lS@vcS{^A;iUq$V(!vUK_3hSk{!a08c6u3Mcr!|BCAgM3ieWRId)ONH`A>AG1i{ zN^oO(z>#H{W+EVAipya3gkv4SGcJdfQriIF0!!|cm7q<6=XqPL9V?3C_^eW?^w*{m zz`}$Oi#^X{64K_yxDFz^FKLKOs}Crp+=qzACkAKk1z5{;JtFcc@Av_1E)9`XS)owq zPL{)oPE6oaR&IoxA+-pRQa*r)SEkR1=+r5@!aRA!iWLhNE?mfsl2aYQ z+>luNX(7RFi$_r;G7*sM4%{>NEdZSBk}xtd(%+8ffvy+02P2hAWqD=-5@Geq)?Fhb zBWJZc2}&t$#rLMfXsv@r2^fF3R@KIbC+$qavSrJTShQ%-$5ux9fiY&uvF^y|7`&iyi;+c z-vj{PZOJvc6qHhmiyS^j=yHMPa8Z(%@Bh^!h*Lm|EaJYWithnYqAS+Xc>%2Md a0sjM|T-hfYA#q+f~(ny8@z>Pu<5xoKcpEbt( zunPH=5Xj^q;-ggn8kYhhZ2VXf~Ux zWk8i8(D(g4LWt98)0T*yw$>gl3$ToG+}8K~6(Pi7M0{UT7Efnu?Y?HSS$J?u5h$f( zVE3m0t7LkVB`@ASYndUU2aGX?i}qEPz?YNOM?}0pA1MlGlA9s}zuUCdZJ_dXuF`7=+SFhK1&&|zU zCxq~cNE87cwLwHnM2*eO&8ODZ*8ZI*E6oZ4;4!T=AH#+&g$T^rJi;*K1v&SSDy4iH z5#KvB+QAK0wblpnWct3p*Ymv7$!T14Gbu!1#)ABW)|!tr1Iyy#;{5#l{40o9sP6&7 zLjZWa)_Oe;gHnp8>%jzxg*LuIN|XVxfB*hVcJ12rcSM}aQ*_AZop!r@S*z80HLqP6 z14zmqnn0eup69)s6cmQxrLN~GrPQ|dGf(Nf=S1`aW6Y=W>hbd`bm*xPm%uM1=`V+ra=}U28qn z9+)hml~R0#TZlh*JxLUr!d0K4%2-epZPV7`VQ*4HZ6Zpk2Cu;h@P$wDRgh3dM#eO5 zYOMoTxy}UcwCNiWRCXFW6DXxTf{1q~MyBu;$aREAT`x^9B6`>ubB{~kTLkj$U}0h5 z25ap+032^L8po53rIhUV>vI%@VHga1d~g9Z&N|&V4lhl;!oetG%)TU!<>lpf2_d8q z;%vL!KE{W3TwGkdXklUD4*>Air~wiE9uXgDx7&}lTCIO(bTajx7uSJE^Uy@b5d^^- zg%Gy^z#RavKPK}}>+9=RV-N%n2qC_l(OHLxenvzcA;iZMqvUMnu8K^mUfMJc!|+F* z=kay(W&n6i2DbZADfI#XTsa6l5p6w=-~V^Q^o?{*ZjfpErtw2Wr%+1yJR)A5H~Nv# zZoL<*wV%#wGfU5f5Z?!Yt5{Dq6h~*#Nw&bx1HgkW%0|Ln>dtDNVRYm}_tq{frMw;y zIgau6EX7^TdRB>xZJ^thtE5i6$##SDzv?>&Dy80It-Ti!Kaw=z|91jeeJ#g_HC9CH4p zwdOd>#EMetKLBuX&OkqCjJbEL1kR8JYWCRWjNte!FbIMx5b-%gyeJDC5#48u`TE4n z4*-->hXCL!12Aq7(UoIg0~t_OmVOuPi|gy_?>u$t)W#$UOp7|k@mOmQH5!etjeQsH zcIX8`aJLYG0lYQ_;+LIH=QC?-YcEZbyr}Q)iQy3d_+a!O5%D?jkTK?A0I-uHaI0Sq z%j||p7#N3?Mt0b=Si1r)~uNp-PB6oM5d9b zsxgh^7*jRKX_j|^z>=eLfxrs{mSmu0a~BJ|;7}bvaxQ`VPHobq4**STty7X=$pN1F z=0XV0+UQGdj$YP|Vj`(8&rH2$69gE&SQHOyrls&s5aLvK-Airm5!ihLb2aZf15>Tm z7{KxOAFQ=2QMfmm8^cqX-(%~0laX;H@&kKN9rNhMpjxpmhS9709f+IavGUX`{ce&G zFPc$O`8A06;WUVRCoBeiI$EWD^Py`n9Bg7=;#RTNKAx6P@AR6(Am^#p+DXQA(6t?f zQYtm*MMQTQW4@b*+evvT<(-K5oit%Pn7})YG2g3%K;QQddY+f|QHH;0tuN2(WSXBV zrTz*4Z%$$)q8nXe#!eQvw6yf5TCK+48R!dP646m(48KD$vqegI6cKNYD?miAcHR7c z%TvsWKo%mUybclX1AuFY=pO*^>vQMMed*-Mll*&Ti&E+hM8t8M>-n1ti1=Fo_^Q^r eFWD+f!T$oxVBzjFl>-p~0000jTdh2phLr&Kn%n<&$&cHUt8r5F)`F ze4A8+SON$z90AyLj=LOYF#%5=9p*52OqLtKWWb&f!xMr%+!-*R!Qp%GfUyT8qv48J zeyGrYEEc))2IC1r&4(bZR!i4<&@ns?f_Xe1#9%^9raOsn*CeBaOy`bj7K|$h5se&I zstF~A0!Bqy43bM|E-;2t^948}BQRWwVTt2il*D5MrisVYfIt8&4p*WIOsjDn zg^R`fAXG!hP&pDL^ae=*x>Bj&`+N9vxI&T8!_$)i!y;dv2ZQbJ$7M5NA0q==zIkus~ zzBM(eaW}goGHxL0DslB2ZAa4$tTRcv)uP7g6O5STrH%)Hb#hO{=w6n0-(rao; z&dQR$sUZg}-E6TcHqQ_c(IJUA?%N%w55u^gY&}~ z8&igwuD#k;jbQ_4%)aBppWL7TvH*fcz{-Lz@m z&E{@vBKEHv&a|)Y1vTjFqiAzp-8pxsEVHhn;`EPlhYoe0+raa5{rO?EQ=`tR+xj5! z<~8IKM_THsvmN2IZJ9M@ADCbf?OaJOvv*~d|4{6PfSH!k+}9f~u1VUP@zAwplgyCM z+7WzasW~#Int7DyyL%*TDD_IWYvJ?#`tDc2eEG9^w3pJ#SOebr`b4(Z#r=1x?a+L%A?dqWt`ECa+gsphCB03hMcd9n!&Zr)#Z};0X@JOl&5}5}tkt#S7{2_uJ^z34nKfhK4TG2d|(1q1nE3Yja2XiYvFg;DkFm_YV0iw+BB!2N!{NJ@m1~cRmPS(bJ_QpNy&U7R z3wm}muI$waurfEgxn$3JPjQ!-K_6x1Lfx4QwJq_m|8j}`z#)fC*7xMU?hcq;!6~p9 tE(LiliZxCzA1+EZ)sGqxsZ2Gsq~xrL81_0wcomoe*jU4yqy35 literal 0 HcmV?d00001 diff --git a/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_lower.png b/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_lower.png new file mode 100644 index 0000000000000000000000000000000000000000..6842e3eaeea9224cd9e87e172dd060e22cbdc2a2 GIT binary patch literal 1819 zcmbVLc~BEq7~dp7B8pXugCdM;@IWNVCYWm>z$PIDB@_^ZsppbxV8vu(vXFqF5si4% zdW|~5C?Fz;^&0D_$jDW-)@o}h9#n-Wm7^%Jjs+F98x`9>82{*-*?sSO@B4k<@4Gg} z$9>`D>FWsqfLBbkGLhONT_4MX`d8yC?o-=DGAfmvN9afkYQg|SOQd07j2=zL5;0Vp zv*HRC1^^5Lo|Hn>d zsv)&efo0&)xh8C0Zd{TkH&Y|kvLnL5Fe^+M&|@SDTJ_5fX4ooYzq1QdXV*244Zc$$ zGiB`eq*B%Kpn@=AppYx#X!x@QpjgV~iy*0Bb}%S__+lO;=J7)~5Fds_aEKKAu-KG0 zlU4^ODpenRQId?EL6Sz8$Fo>0TuTU-Fs1YOQmK>&33vhlhtlAfa||SE{zHE6IQzc}Uh12*cDSXg1gP?9hu5ybML4v5bnNWz>!7(s=?RdFF0 zn1UO$gvC75RigJ1D=`zEjcHXTLJz*nF^mtxL8KFiAPp+uXfz@bN35MK;h-Uqlp_)e zAWSC_Xr!+A-{ZCaCwDxG5YN>l|7n_`E2HBr1Z1(yo>%apUEPkMM z+|xr$i}JVNb+P06|6unZ{=%!;gz^Ui<1cJKl-JxHms`@`V2M^IgtYNw$;Yc#26@^G z=2RqB`|Y}>ORfkq+ucj)1{>4iS1S$%lBd`sMpkn4l@7ae&A}x!IPd(}3stX;<{h*y zr`|cbIGpJ?G?o5~V}WCz>eb33=C`n$c=`z2=r($vpf#fYYWsNSwx_MAWTzVd56-l1 zz42yyV_gB0)p+7YC+n#9N%zvG67BA@e)iQk{^txFl~qI#O~)Uz@6)m@HTN7jO|l2kpNu?@Z1iF!t~H}(8w1M{-t ziGk6!S#hesyt=IyTk<{Rhc`8gfrU9mmCk%x?~*4CnG;xd_cxh=yB-x2!Y8$h-f(Mo zuc?0w%NX_ggl9>opI5hMF&AHsY`^ZW8)XYU_`|%@H_IEF^;X&7H5=4&W#=s5F25yE zt-2SKcaP0#^I|=Y17qE7p;dn6&P73RP0X0-6ItEe^s34o&DASB+YTS7oaE*xq#=_V z2+PRQZ|10vOb^T}+;r^N?~%73c}H@RQ*;Bq)t3$&%XE4$Wq+0a4X#k~!MEpt1h(dTK6kAyQzlW$O@e}4S2b? zYzA%pt-f8GTm4SNyv{dt?fKd9d>&q0E@5`H(Ky?aLEX!MdH`Nki$yOA*a=W8CMr&O IOun?>A12AI1poj5 literal 0 HcmV?d00001 diff --git a/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_space.png b/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_key_space.png new file mode 100644 index 0000000000000000000000000000000000000000..4ecade1f4406051d129985f898e1bedb1a3a25b7 GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}EWU|{_0>Eakt5%+ecp?9*Qh}-+Q z*KbF0o0&DvWR#9PXmxkpqx!&09S`CKMYdgxx{-7+>C>)~O>ehF%{CVjyqIvMYTsm| zC6>?Ue)s+K=$QAd2Jg$v`78=@jEn*eI~WUV#2Ofm@OC`j%)p|M=O8nmRe_<9`Oyy@ z2F4C)f#d0n91eFHEbO=)7#^`5`54K-Bw()4cbvFCoSpP9RVg6*{^e%qxc z#oyO%-?8=D{L3%DloxZgG$!@zcjB1teZBIT>9vkSpq$KTcM5;^c8 zk>Rj`&#!&;-yc5f>0o?1M?I}Jetk754R_&i6+Z@%t{$ zzkk~D{_LAko1Zk)rad_5zW8GQy4QMlYUeIq@HXmR&B^$NfAh~jXZQd8b>e>&xhvOz Pser-L)z4*}Q$iB}1&jC( literal 0 HcmV?d00001 diff --git a/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_view_bg.9.png b/keybordlib/src/main/res/drawable-xxhdpi/king_keyboard_view_bg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..c8dfb1d30674e5bb877468d78b0daaa6448a60a1 GIT binary patch literal 979 zcmaJ=O>5LZ7!GKyEffVsTu;;VqR`FcW1BAZhyvfZs7Y6Fv*d3m1qecq2bY_2_=o4qs3Fw9(i z)o#)K5?$A>UZMZ_AN`MXD-owd*6}9E+ypVEkGlxeLw5_ckn0b2Kci(znh)9?(rG-> zJsfgwf#LEnrfh~;UddzE+eQR*(N+){?8)(a76iV*ZY(wUMqEX`V0AA+>w9Z$Z*SYv ze0JplSk86oAVkCkdAJj$dTy{IzdAh^X2^mOglrq^si;n)392|jV2P8u?}IDWn3n4IN5@NrfHBD zp(vIqqMQyQ;^yTjU6^>VQR*c@OadH%!lT>8ePXaw>1hgK+-RIRj?!_VXvtvi#!%pR z7=}e&BXmky=&y`((P?`SBiKSI?k67Y$L7L>Olx;>paMwM=r;EHA&N-dHdt!m{J_^m z)sj@r6eLBq1i>*iOSLW2aTHr#QcR~Z;nt&+xRHk@+~6;lzmTg}6XX({v@zb9bg$XN z1gAY5gK8Dr-3THdXX(A7vSXnr37#Rpmf#SKN}~togs-W*D4UWc2{o;dZi<4c@`5HR zsv}z!jU99SbJ8AjtN-OfDh3vP{in|p6CIx7cs8~4ayIuUqQjohsh)b-yh&fgYTY*5 z`PXiDe}4!LX6$@0*gepO!{<}4%*u^+NqSPcegx9a;rFBY^vg{A>)Y}4TkTEd!|*se j`8|FA)~Ed$p1FLRd9hPkEWiHwxOirDXU%?RJw5mXOP(=& literal 0 HcmV?d00001 diff --git a/keybordlib/src/main/res/drawable/king_keyboard_done_key_bg.xml b/keybordlib/src/main/res/drawable/king_keyboard_done_key_bg.xml new file mode 100644 index 0000000..06268a7 --- /dev/null +++ b/keybordlib/src/main/res/drawable/king_keyboard_done_key_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/drawable/king_keyboard_key_bg.xml b/keybordlib/src/main/res/drawable/king_keyboard_key_bg.xml new file mode 100644 index 0000000..06b04c6 --- /dev/null +++ b/keybordlib/src/main/res/drawable/king_keyboard_key_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/drawable/king_keyboard_none_key_bg.xml b/keybordlib/src/main/res/drawable/king_keyboard_none_key_bg.xml new file mode 100644 index 0000000..fbcb125 --- /dev/null +++ b/keybordlib/src/main/res/drawable/king_keyboard_none_key_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/drawable/king_keyboard_special_key_bg.xml b/keybordlib/src/main/res/drawable/king_keyboard_special_key_bg.xml new file mode 100644 index 0000000..8ee8cb6 --- /dev/null +++ b/keybordlib/src/main/res/drawable/king_keyboard_special_key_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/layout/king_keyboard_container.xml b/keybordlib/src/main/res/layout/king_keyboard_container.xml new file mode 100644 index 0000000..07bb908 --- /dev/null +++ b/keybordlib/src/main/res/layout/king_keyboard_container.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/values-zh/strings.xml b/keybordlib/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..6be2a21 --- /dev/null +++ b/keybordlib/src/main/res/values-zh/strings.xml @@ -0,0 +1,6 @@ + + + 完成 + 更多 + 返回 + \ No newline at end of file diff --git a/keybordlib/src/main/res/values/attrs.xml b/keybordlib/src/main/res/values/attrs.xml new file mode 100644 index 0000000..21ad7eb --- /dev/null +++ b/keybordlib/src/main/res/values/attrs.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/values/colors.xml b/keybordlib/src/main/res/values/colors.xml new file mode 100644 index 0000000..bbee4be --- /dev/null +++ b/keybordlib/src/main/res/values/colors.xml @@ -0,0 +1,26 @@ + + + + #ff333333 + + #ff333333 + + #ffeeeeee + + #7f999999 + + #ff333333 + + #ffffffff + #ffbbbbbb + + #ffa8afbf + #ff989faf + + #ff0477ff + #ff0467ef + + #ffefefef + #ffefefef + + \ No newline at end of file diff --git a/keybordlib/src/main/res/values/dimens.xml b/keybordlib/src/main/res/values/dimens.xml new file mode 100644 index 0000000..921a721 --- /dev/null +++ b/keybordlib/src/main/res/values/dimens.xml @@ -0,0 +1,22 @@ + + + + 48dp + 101dp + 154dp + + 5dp + 10dp + + 6dp + 6dp + + 56dp + + 20sp + + 20sp + + 16sp + + \ No newline at end of file diff --git a/keybordlib/src/main/res/values/strings.xml b/keybordlib/src/main/res/values/strings.xml new file mode 100644 index 0000000..c77cb45 --- /dev/null +++ b/keybordlib/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + Done + More + Back + diff --git a/keybordlib/src/main/res/xml/king_keyboard_id_card.xml b/keybordlib/src/main/res/xml/king_keyboard_id_card.xml new file mode 100644 index 0000000..73b79b1 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_id_card.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_letter.xml b/keybordlib/src/main/res/xml/king_keyboard_letter.xml new file mode 100644 index 0000000..cffe8c3 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_letter.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_letter_number.xml b/keybordlib/src/main/res/xml/king_keyboard_letter_number.xml new file mode 100644 index 0000000..e8babb9 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_letter_number.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_license_plate.xml b/keybordlib/src/main/res/xml/king_keyboard_license_plate.xml new file mode 100644 index 0000000..d71089e --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_license_plate.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_license_plate_more.xml b/keybordlib/src/main/res/xml/king_keyboard_license_plate_more.xml new file mode 100644 index 0000000..4ab62f2 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_license_plate_more.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_license_plate_number.xml b/keybordlib/src/main/res/xml/king_keyboard_license_plate_number.xml new file mode 100644 index 0000000..f0443a3 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_license_plate_number.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_license_plate_province.xml b/keybordlib/src/main/res/xml/king_keyboard_license_plate_province.xml new file mode 100644 index 0000000..8a2ee0c --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_license_plate_province.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_lowercase_letter_only.xml b/keybordlib/src/main/res/xml/king_keyboard_lowercase_letter_only.xml new file mode 100644 index 0000000..9a426c1 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_lowercase_letter_only.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_normal.xml b/keybordlib/src/main/res/xml/king_keyboard_normal.xml new file mode 100644 index 0000000..4b4ca21 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_normal.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_normal_mode_change.xml b/keybordlib/src/main/res/xml/king_keyboard_normal_mode_change.xml new file mode 100644 index 0000000..7997424 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_normal_mode_change.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_normal_more_symbol.xml b/keybordlib/src/main/res/xml/king_keyboard_normal_more_symbol.xml new file mode 100644 index 0000000..09ba3a8 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_normal_more_symbol.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_number.xml b/keybordlib/src/main/res/xml/king_keyboard_number.xml new file mode 100644 index 0000000..8674ef8 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_number.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_number_decimal.xml b/keybordlib/src/main/res/xml/king_keyboard_number_decimal.xml new file mode 100644 index 0000000..5ffc8e6 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_number_decimal.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_phone.xml b/keybordlib/src/main/res/xml/king_keyboard_phone.xml new file mode 100644 index 0000000..ffca546 --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_phone.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/main/res/xml/king_keyboard_uppercase_letter_only.xml b/keybordlib/src/main/res/xml/king_keyboard_uppercase_letter_only.xml new file mode 100644 index 0000000..00dd27a --- /dev/null +++ b/keybordlib/src/main/res/xml/king_keyboard_uppercase_letter_only.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keybordlib/src/test/java/com/king/keyboard/ExampleUnitTest.kt b/keybordlib/src/test/java/com/king/keyboard/ExampleUnitTest.kt new file mode 100644 index 0000000..3fa56f9 --- /dev/null +++ b/keybordlib/src/test/java/com/king/keyboard/ExampleUnitTest.kt @@ -0,0 +1,18 @@ +package com.king.keyboard + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } + +} diff --git a/settings.gradle b/settings.gradle index e0fc324..340c83a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = "Pass" include ':app' include ':mylibrary' +include ':keybordlib'