全体包
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 Room
implementation "androidx.room:room-ktx:$room_version"
// optional - Test helpers
testImplementation "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) {
@PrimaryKey
var account: Long = account
@ColumnInfo(name = "password")
var psd: String = psd
}
- Dao层
@Dao //Database access object
interface UserDao {
@Insert
fun insertUsers(vararg users: User?)
@Update//返回修改的记录条数
fun updateUsers(vararg users: User?) //通过id进行替换内容
@Delete
fun 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则返回多个Dao
companion object {
var INSTANCE: UserDatabase? = null
@Synchronized//如果有多个客户端 且同时instance时保证不会有碰撞 只有一个instance生成
open fun getDatabase(context: Context): UserDatabase? {
//静态类的静态方法写open
if (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?=null
lateinit var userdao: UserDao
override 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 listener
open 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:FindUserModel
var loginFlag:Boolean=false
override 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=true
var 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:FindUserModel
var registFlag:Boolean=false
override 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=true
var 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=false
var 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?=null
var goodsname: String?=null
var goodssort: String?=null
var goodsprice: Int?=null
var goodspricecount: Float?=null
var goodssum: Int?=null
var goodsstate: Boolean=true
var goodsimageuristring: String?=null
var goodsimageuri: Uri?=null
companion object {
val TAKE_PHOTO = 1
val 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:ss
val 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? = null
val uri = data.data
Log.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] // 解析出数字格式的id
val selection = MediaStore.Images.Media._ID + "=" + id
imagePath = 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.data
val 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=path
return 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 {
//创建holder
val 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.goodsname
textViewLikes.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=goodsAdapter
layoutManager= 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=false
fun saveSlash( slash: Boolean){
Thread {
itemmemory.edit().putBoolean("slashs",slash).apply()
}.start()
}
fun loadSlash():Boolean{
defaultboolvalue =itemmemory.getBoolean("slashs",deafultbool)
return defaultboolvalue
}
}
还有第二个地图定位,就不暴露位置了emmmm。
5. 补充