源
- APP下载地址: https://wws.lanzous.com/iAKuok3dvah密码:e306
- Github: https://github.com/kirikaTowa/TBzcibDZY
全体包
implementation 'io.supercharge:shimmerlayout:2.1.0'implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'//添加anko库implementation 'org.jetbrains.anko:anko:0.10.8'//添加ButtonBar组件implementation 'com.roughike:bottom-bar:2.3.1'implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-beta01'def room_version = "2.2.5"implementation "androidx.room:room-runtime:$room_version"kapt "androidx.room:room-compiler:$room_version"// optional - Kotlin Extensions and Coroutines support for Roomimplementation "androidx.room:room-ktx:$room_version"// optional - Test helperstestImplementation "androidx.room:room-testing:$room_version"implementation 'com.roughike:bottom-bar:2.3.1'implementation 'androidx.fragment:fragment-ktx:1.2.2'implementation 'com.github.bumptech.glide:glide:4.10.0'
1. 登录模块
1.界面绘制,圆角和方块弧通过drawable控制shape绘制,背景图来自Iphone壁纸
- 假定默认登录注册密码为root 12345
2. Room初始三件套(实体类,Dao层,DataBase库)
- 实体类(只有主键可设定自增,一般登录注册是从网上接口拿数据的)
@Entity//Room表示创建实体类
class User(account: Long, psd: String) {
@PrimaryKeyvar account: Long = account@ColumnInfo(name = "password")var psd: String = psd}
- Dao层
@Dao //Database access object
interface UserDao {
@Insertfun insertUsers(vararg users: User?)@Update//返回修改的记录条数fun updateUsers(vararg users: User?) //通过id进行替换内容@Deletefun deleteUsers(vararg users: User?)@Query("DELETE FROM User")//删除所有内容fun deleteAllUsers();@Query("SELECT * FROM User ")//降序查询fun getAllUsers():List<User>@Query("SELECT * FROM User WHERE account In(:account)")//查询账号是否存在fun getUser(account:Long):LiveData<User>@Query("SELECT * FROM User ")fun getAllWordsLive():LiveData<List<User>>
}
- Database控制
@Database(entities = [User::class],version = 2,exportSchema = false)abstract class UserDatabase: RoomDatabase() {
//若有多个entities则返回多个Daocompanion object {
var INSTANCE: UserDatabase? = null@Synchronized//如果有多个客户端 且同时instance时保证不会有碰撞 只有一个instance生成open fun getDatabase(context: Context): UserDatabase? {
//静态类的静态方法写openif (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.applicationContext, UserDatabase::class.java, "user_database").fallbackToDestructiveMigration()//.allowMainThreadQueries()//先允许主线程运行//.addMigrations(MIGRATION_6_7).build()}return INSTANCE}}abstract fun getUserDao(): UserDao
}
- 导入自己喜欢的BaseActivity包,目前userdatabase对象和userdao只是为了登录注册服务的,以后可以尝试一下范型。并且单是抽取积累的话登录注册基类+封装也挺好的看个人需求。
//所有Activity基类
abstract class BaseActivity: AppCompatActivity(), AnkoLogger {
private var userdatabase: UserDatabase?=nulllateinit var userdao: UserDaooverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)setContentView(getLayoutid())//基类抽取界面组initListener()//子类中并非一定要实现,创建一个function即可initDate()//数据支持}// 初始化数据,非私有加入open关键字才能复写open protected fun initDate() {
userdatabase=UserDatabase.getDatabase(applicationContext)!!userdao=userdatabase?.getUserDao()!!}// adapter listeneropen protected fun initListener(){
}//创建抽象类以供获取Layout,abstract不用加abstract fun getLayoutid():Int//在基类线中弹toast解决线的问题 1?protected fun myToast(msg:String){
runOnUiThread{
toast(msg)}}//开启activity并且finish当前界面,添加内联支持inline 与reified//进入看发现限制是Activity或Activity子类 reified用于找到传入泛型具体的类 下面内部就可以获取Tclass名(所以直接传泛型无法获取名称)inline fun <reified T:BaseActivity>startActivityAndFinish(){
startActivity<T>()finish()}
}
- 具体登录代码,用onclick绑定界面,写监听怪麻烦的,以后用databind时改也方便。
class LoginActivity : BaseActivity() {
lateinit var userviewModel:FindUserModelvar loginFlag:Boolean=falseoverride fun getLayoutid(): Int {
return R.layout.activity_login}override fun initDate() {
super.initDate()userviewModel = ViewModelProvider(this).get(FindUserModel::class.java)}fun LoginUser(v:View) {
loginFlag=truevar textcount = Account_text.text.toString()var textpws = Password_text.text.toString()if (textcount.isEmpty()) {
myToast("请输入用户名")}if (textpws.isEmpty()) {
myToast("请输入密码")}if (!textcount.isEmpty() && !textpws.isEmpty()) {
userviewModel.findUser = userdao.getUser(textcount.toLong())userviewModel.findUser?.let {
userviewModel.findUser!!.observe(this, Observer {
if (loginFlag==true){
if ((it!=null)){
if ( it.psd.equals(textpws)) {
myToast("登录成功")startActivityAndFinish<MainActivity>()} else {
myToast("账号或密码错误")}}else{
myToast("用户不存在")}}})}}}fun IntoRegist(v:View){
startActivity<RegistActivity>()}}
2. 注册模块
1.界面绘制,圆角和方块弧通过drawable控制shape绘制
- 代码
class RegistActivity : BaseActivity() {
lateinit var userviewModel:FindUserModelvar registFlag:Boolean=falseoverride fun getLayoutid(): Int {
return R.layout.activity_regist}override fun initDate() {
super.initDate()userviewModel = ViewModelProvider(this).get(FindUserModel::class.java)}fun RegistUser(v: View) {
//会自动绑定get方法 插入成功后会自动get 设置个变量registFlag=truevar textcount = Input_account.text.toString()var textpws = Input_password.text.toString()var textpwsre = Input_passwordreput.text.toString()if (textcount.isEmpty()){
myToast("请输入用户名") }if (textpws.isEmpty()){
myToast("请输入密码") }if (textpwsre.isEmpty()){
myToast("请确认密码") }if (!textpwsre.equals(textpws)){
myToast("两次密码输入不一致") }if (!textcount.isEmpty() && !textpws.isEmpty() && !textpwsre.isEmpty() && textpwsre.equals(textpws)) {
userviewModel.findUser = userdao.getUser(textcount.toLong())userviewModel.findUser?.let {
userviewModel.findUser!!.observe(this, Observer {
if (registFlag==true){
if ((it!=null)){
Input_account.text.clear()myToast("账户已存在")}else{
registFlag=falsevar user = User(textcount.toLong(), textpws)InsertAsyncTask(userdao).execute(user)//增设查询该条记录myToast("插入成功")Handler().postDelayed(Runnable {
startActivityAndFinish<LoginActivity>() }, 1000)}}})}}}internal class InsertAsyncTask(private val userdao: UserDao) : AsyncTask<User?, Unit, Unit>() {
//在后台工作override fun doInBackground(vararg params: User?) {
publishProgress()//该方法用于一段时间报告工作进度userdao.insertUsers(*params)}}
}
3.FragmentUtil控制首页与bottombar切换
1. 在fragment中获取AC和对应Context方式,
2. 建立App类,用于处理主线程回调,并且声明
- android:name ,它是用来app启动时来关联一个application的,默认关联的是android.app.Application,当app启动时,会默认创建一个application的实例 ,当在Activity中调用getApplication()方法时 ,就会返回这个实例,所以这个android:name 指定的类就有点似于全局变量的作用吧 , 用来存储数据供给整个 Activity 使用
class App: Application() {
companion object{
val handler= Handler()//主线程静态handler}override fun onCreate() {
super.onCreate()}
}
- 样例
4. 建立对应的货物三件套
1. 三件套
2.货物实体类
@Entity
class Goods(goodscode: Int, goodsname: String, goodssort: String, goodsprice: Int,goodspricecount: Float, goodssum: Int, goodsstate: Boolean, goodsimageuri: String?
) {
@PrimaryKey(autoGenerate = true)var id = 0@ColumnInfo(name = "goods_code")var goodscode: Int = goodscode@ColumnInfo(name = "goods_name")var goodsname: String = goodsname@ColumnInfo(name = "goods_sort")var goodssort: String = goodssort@ColumnInfo(name = "goods_price")var goodsprice: Int = goodsprice@ColumnInfo(name = "goods_count")var goodspricecount: Float = goodspricecount//折扣 0.23@ColumnInfo(name = "goods_sum")var goodssum: Int = goodssum//控制上架下架@ColumnInfo(name = "goods_state")var goodsstate: Boolean = goodsstate@ColumnInfo(name = "goods_imageuri")var goodsimageuri:String?=goodsimageuri
}
2.BaseFragment中初始化dao和database,来到主fragment,点击插入,搞定。
class HomeFragment: BaseFragment() {
override fun initView(): View? {
return View.inflate(context, R.layout.fragment_home, null)}override fun initData() {
super.initData()Insert_button.setOnClickListener {
var goods:Goods = Goods(1,"充电宝","10",100,1.0f,200,true,null)var goods1:Goods = Goods(1,"充电宝","10",100,1.0f,200,true,null)goodsdao.insertGoods( goods, goods1)updateView();}/* Delete_button.setOnClickListener {var word:Word = Word("Word","世界")word.setId(40)worddao.deleteWords(word)updateView()}*/Deleteall_button.setOnClickListener{
goodsdao.deleteAllGoods()updateView()}
/* Update_button.setOnClickListener {var word:Word = Word("Word","世界")word.setId(40)worddao.updateWords(word)updateView()}*/}fun updateView(){
var list:List<Goods> =goodsdao.getAllGoods()var text=""for (i in (0 until list.size)){
var goods:Goods =list.get(i)text+=""+goods.goodsname+goods.goodsprice;}Text_goodsname.text=text}}
3. 迁移,将商品上架放其他位置-第三个,然后首页展示,测试没问题
class InGoodsFragment: BaseFragment() {
var goodsCode: Int?=nullvar goodsname: String?=nullvar goodssort: String?=nullvar goodsprice: Int?=nullvar goodspricecount: Float?=nullvar goodssum: Int?=nullvar goodsstate: Boolean=truevar goodsimageuristring: String?=nullvar goodsimageuri: Uri?=nullcompanion object {
val TAKE_PHOTO = 1val CHOOSE_PHOTO = 2}override fun initView(): View? {
return View.inflate(context, R.layout.fragment_ingoods, null)}override fun initListener() {
Switch_state.setOnCheckedChangeListener {
buttonView, isChecked ->goodsstate=isChecked}Button_inphoto.setOnClickListener {
if (getActivity()?.applicationContext?.let {
it1 -> ContextCompat.checkSelfPermission(it1, Manifest.permission.WRITE_EXTERNAL_STORAGE) } !== PackageManager.PERMISSION_GRANTED) {
getActivity()?.let {
it1 -> ActivityCompat.requestPermissions(it1, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) }} else {
openAlbum()}}Button_incamera.setOnClickListener {
val simpleDateFormat = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss") // HH:mm:ssval date = Date(System.currentTimeMillis())var changeCode=simpleDateFormat.format(date)changeCode=changeCode+".jpg"println("1111111111111${
changeCode}")// 1. 创建File对象,用于存储拍照后的图片val outputImage = File(getActivity()?.externalCacheDir, changeCode)println("11111111111"+outputImage)try {
if (outputImage.exists()) {
outputImage.delete()}outputImage.createNewFile()} catch (e: IOException) {
e.printStackTrace()}if (Build.VERSION.SDK_INT < 24) {
goodsimageuri = Uri.fromFile(outputImage)} else {
goodsimageuri = getActivity()?.applicationContext?.let {
it1 ->FileProvider.getUriForFile(it1,"com.ywjh.cameraalbumtest.fileprovider",outputImage)}}// 启动相机程序val intent = Intent("android.media.action.IMAGE_CAPTURE")intent.putExtra(MediaStore.EXTRA_OUTPUT, goodsimageuri)startActivityForResult(intent, TAKE_PHOTO)}Button_submitgoods.setOnClickListener {
if (In_code.text!=null && In_goodsname.text!=null && In_goodssort.text!=null && In_goodsprice!=null&& In_goodscount.text!=null && In_goodssum.text!=null && goodsimageuristring!=null){
goodsCode=In_code.text.toString().toInt()goodsname=In_goodsname.text.toString()goodssort=In_goodssort.text.toString()goodsprice=In_goodsprice.text.toString().toInt()goodspricecount=In_goodssort.text.toString().toFloat()goodssum=In_goodssum?.text.toString().toInt()var goods: Goods = Goods(goodsCode!!,goodsname!!,goodssort!!,goodsprice!!, goodspricecount!!, goodssum!!, goodsstate,goodsimageuristring)goodsdao.insertGoods( goods)}else{
/* println("1111"+In_code.text!=null)println("1112"+ In_goodsname.text!=null)println("1113"+In_goodssort.text!=null)println("1114"+In_goodsprice==null)println("1115"+In_goodscount.text)println("1116"+In_goodssum.text)println("1117"+goodsimageuristring)*/myToast("请补全信息及上传图片")}}}override fun initData() {
super.initData()}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
TAKE_PHOTO -> if (resultCode == Activity.RESULT_OK) {
try {
goodsimageuristring=goodsimageuri.toString()// 将拍摄的照片显示出来val bitmap = BitmapFactory.decodeStream(goodsimageuristring?.let {
getActivity()?.contentResolver?.openInputStream(it.toUri())})imageView.setImageBitmap(bitmap)} catch (e: Exception) {
e.printStackTrace()}}CHOOSE_PHOTO -> if (resultCode == Activity.RESULT_OK) {
// 判断手机系统版本号if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片if (data != null) {
handleImageOnKitKat(data)}} else {
// 4.4以下系统使用这个方法处理图片if (data != null) {
handleImageBeforeKitKat(data)}}}}}//4.4后 判断封装情况@TargetApi(19)private fun handleImageOnKitKat(data: Intent) {
var imagePath: String? = nullval uri = data.dataLog.d("TAG", "handleImageOnKitKat: uri is $uri")if (DocumentsContract.isDocumentUri(getActivity(), uri)) {
// 如果是document类型的Uri,则通过document id处理val docId = DocumentsContract.getDocumentId(uri)if ("com.android.providers.media.documents" == uri!!.authority) {
val id = docId.split(":".toRegex()).toTypedArray()[1] // 解析出数字格式的idval selection = MediaStore.Images.Media._ID + "=" + idimagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection)} else if ("com.android.providers.downloads.documents" == uri.authority) {
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(docId))imagePath = getImagePath(contentUri, null)}} else if ("content".equals(uri!!.scheme, ignoreCase = true)) {
// 如果是content类型的Uri,则使用普通方式处理imagePath = getImagePath(uri, null)} else if ("file".equals(uri.scheme, ignoreCase = true)) {
// 如果是file类型的Uri,直接获取图片路径即可imagePath = uri.path}displayImage(imagePath) // 根据图片路径显示图片}private fun handleImageBeforeKitKat(data: Intent) {
val uri = data.dataval imagePath = getImagePath(uri, null)displayImage(imagePath)}fun openAlbum() {
val intent = Intent("android.intent.action.GET_CONTENT")intent.type = "image/*"startActivityForResult(intent, CHOOSE_PHOTO) // 打开相册}override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) {
1 -> if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum()} else {
myToast("You denied the permission")}}}private fun getImagePath(uri: Uri?, selection: String?): String? {
var path: String? = null// 通过Uri和selection来获取真实的图片路径val cursor = getActivity()?.contentResolver?.query(uri!!, null, selection, null, null)if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))}cursor.close()}goodsimageuristring=pathreturn path}private fun displayImage(imagePath: String?) {
if (imagePath != null) {
val bitmap = BitmapFactory.decodeFile(imagePath)imageView.setImageBitmap(bitmap)} else {
myToast("failed to get image")}}}
class HomeFragment: BaseFragment() {
override fun initView(): View? {
return View.inflate(context, R.layout.fragment_home, null)}override fun initData() {
super.initData()Insert_button.setOnClickListener {
var goods:Goods = Goods(1,"充电宝","10",100,1.0f,200,true,null)var goods1:Goods = Goods(1,"充电宝","10",100,1.0f,200,true,null)goodsdao.insertGoods( goods, goods1)updateView();}/* Delete_button.setOnClickListener {var word:Word = Word("Word","世界")word.setId(40)worddao.deleteWords(word)updateView()}*/Deleteall_button.setOnClickListener{
goodsdao.deleteAllGoods()updateView()}
/* Update_button.setOnClickListener {var word:Word = Word("Word","世界")word.setId(40)worddao.updateWords(word)updateView()}*/}fun updateView(){
var list:List<Goods> =goodsdao.getAllGoods()var text=""for (i in (0 until list.size)){
var goods:Goods =list.get(i)text+=""+goods.goodsname+goods.goodsprice;}Text_goodsname.text=text}}
4. 首页进行列表显示
- 建立adapter
class GoodsAdapter: ListAdapter<Goods,GoodsAdapter.MyViewHolder>(DIFFCALLBACK) {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)object DIFFCALLBACK : DiffUtil.ItemCallback<Goods>() {
override fun areItemsTheSame(oldItem: Goods, newItem: Goods): Boolean {
return oldItem === newItem //判断是否为同一个对象 三等于号}override fun areContentsTheSame(oldItem: Goods, newItem: Goods): Boolean {
return oldItem.id == newItem.id//判断内容是否相同}}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
//创建holderval holder = MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.goods_cell, parent, false))return holder}override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val photoItem=getItem(position)//使用with语句with(holder.itemView) {
shimmerLayoutCell.apply {
setShimmerColor(0x55FFFFFF)setShimmerAngle(0)//闪动角度startShimmerAnimation()}textViewUser.text=photoItem.goodsnametextViewLikes.text="¥${
photoItem.goodsprice}"textViewFavorites.text="打折:${
photoItem.goodspricecount}"textViewFavorites2.text="存货:${
photoItem.goodssum}"textViewFavorites4.text="种别码:${
photoItem.goodscode}"textViewFavorites5.text="id:${
photoItem.id}"}Glide.with(holder.itemView).load(getItem(position).goodsimageuri)//photo对象.placeholder(R.drawable.photo_placeholder).listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?,model: Any?,target: Target<Drawable>?,dataSource: DataSource?,isFirstResource: Boolean): Boolean {
return false.also {
holder.itemView.shimmerLayoutCell?.stopShimmerAnimation() }//判断空, 图片未加载完全就切走 但listenrer还在监听}override fun onLoadFailed(e: GlideException?,model: Any?,target: com.bumptech.glide.request.target.Target<Drawable>?,isFirstResource: Boolean): Boolean {
return false//都return false 不然不显图}//监听加载完成后停止shimmer}).into(holder.itemView.imageView)//加载上去}}
- 设置数据源
class HomeFragment: BaseFragment() {
override fun initView(): View? {
return View.inflate(context, R.layout.fragment_home, null)}override fun initData() {
super.initData()var list:List<Goods> =goodsdao.getAllGoods()val goodsAdapter = GoodsAdapter()recycleView.apply{
adapter=goodsAdapterlayoutManager= GridLayoutManager(requireContext(),2)//两列}goodsAdapter.submitList(list)}}
- sharePerfence储存
class MyData(private val context: Context) {
val memoryname by lazy {
context.resources.getString(R.string.Login_state) }val itemmemory by lazy{
context.getSharedPreferences(memoryname,Context.MODE_PRIVATE)}val deafultbool by lazy {
context.resources.getBoolean(R.bool.default_bool) }var defaultboolvalue=falsefun saveSlash( slash: Boolean){
Thread {
itemmemory.edit().putBoolean("slashs",slash).apply()}.start()}fun loadSlash():Boolean{
defaultboolvalue =itemmemory.getBoolean("slashs",deafultbool)return defaultboolvalue}
}
还有第二个地图定位,就不暴露位置了emmmm。
5. 补充