# 頁面跳轉 要記得先setOnclickLisnter 在寫入code 注意是第二頁是用新增Activity不是layout ``` val intent = Intent(this,頁面名稱::class.java) startActivity(intent) ``` # 登入系統實作 第一步:創建kotlin/class 並命名DBHelper 第二步:在class DBHelper後加上 (context: Context):SQLiteOpenHelper(context,"Userdata",null,1) 按alt + enter會自動新增onCreate跟onUpgrade 第三步在onCreate及onUpgrade打入以下code ``` override fun onCreate(p0: SQLiteDatabase?) { p0?.execSQL("create table Userdata (username TEXT primary key,password TEXT)") } override fun onUpgrade (p0: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { p0?.execSQL("drop table if exists Userdata") } ``` 第四步建立insertData(功能=將帳號及密碼存入Userdata中) ``` fun insertdata(username: String,password:String): Boolean { val p0 =this.writableDatabase val cv =ContentValues() cv.put("username",username) cv.put("password",password) val result =p0.insert("Userdata",null,cv) if (result==-1.toLong()){ return false } return true } ``` 第五步建立checkserpass(查詢帳密是否在資料庫內) ``` fun checkserpass(username: String,password: String): Boolean { val p0 = this.writableDatabase val query ="select * from Userdata where username ='$username'and password ='$password'" val cursor = p0.rawQuery(query,null) if (cursor.count<=0){ cursor.close() return false } cursor.close() return true } ``` 再來回到XML檔,新增一個登入 一個註冊 一個為登入後的頁面 先做註冊,拉3個EDITTEXT 一個是帳號一個是密碼,一個是再次輸入密碼 注意!!!!!每個edittext都要設定id ``` private lateinit var uname: EditText private lateinit var pword : EditText private lateinit var cpword:EditText private lateinit var signupbtn:Button private lateinit var db:DBHelper private lateinit var ftvtn:FloatingActionButton 先宣告每個button及edittext跟資料庫 --- class signupActivity : AppCompatActivity() { @SuppressLint("MissingInflatedId") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_signup) uname =findViewById(R.id.editTextpersonname) pword =findViewById(R.id.editTextpersonPassword1) cpword =findViewById(R.id.editTextpersonPassword2) db= DBHelper(this) ftvtn=findViewById(R.id.floatingActionButton3) signupbtn=findViewById(R.id.button2) signupbtn.setOnClickListener{ val unametext = uname.text.toString() val pwordtext = pword.text.toString() val cpwordtext = cpword.text.toString() val savedata = db.insertdata(unametext,pwordtext) findviewbyid每個變數名稱 並將edittext的值傳為字串 --- if (TextUtils.isEmpty(unametext)||TextUtils.isEmpty(pwordtext)||TextUtils.isEmpty(cpwordtext)){ Toast.makeText(this,"Add username password & confirmpassword",Toast.LENGTH_SHORT).show() //當帳號或密碼其中之一為空, 創建toast訊息 add username password& confirmpassword }else{if (pwordtext==cpwordtext){ if (savedata==true){ Toast.makeText(this,"signup successful",Toast.LENGTH_SHORT).show() //當密碼等於再次輸入密碼顯示登入成功並跳轉到登入頁面 val intent =Intent(this,loginActivity::class.java) startActivity(intent) } else{ Toast.makeText(this,"user exists",Toast.LENGTH_SHORT).show() } } else{ Toast.makeText (this,"password not match",Toast.LENGTH_SHORT).show() } } } ftvtn.setOnClickListener{ val intent =Intent(this,MainActivity::class.java) startActivity(intent) //返回button } } } --- ``` 再來做登入頁面 拉兩個edittext並給其id ``` private lateinit var loginbtn:Button private lateinit var edituser: EditText private lateinit var editpword: EditText private lateinit var dbh:DBHelper private lateinit var ftbtn: FloatingActionButton **//宣告名稱及其對應物件** class loginActivity : AppCompatActivity() { @SuppressLint("MissingInflatedId") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) loginbtn = findViewById(R.id.button) edituser =findViewById(R.id.editTextText2) editpword = findViewById(R.id.editTextTextPassword2) dbh = DBHelper(this) ftbtn =findViewById(R.id.floatingActionButton) loginbtn.setOnClickListener{ val useredtx = edituser.text.toString() val passedtx = editpword.text.toString() 將每個名稱對到各自id並將輸入方塊的值轉為字串 --- if(TextUtils.isEmpty(useredtx)||TextUtils.isEmpty(passedtx)) {Toast.makeText (this,"please enter username and password",Toast.LENGTH_SHORT).show() 當輸入方塊其中之一為空顯示please enter username and password } else{ val checkuser = dbh.checkserpass(useredtx,passedtx) 使用dbhelper的checkserpass功能去將輸入值與資料庫比對 if (checkuser==true){ Toast.makeText (this,"login successful",Toast.LENGTH_SHORT).show() val intent =Intent(this,AfterenterActivity::class.java) startActivity(intent) } else{ Toast.makeText (this,"wrong username or password",Toast.LENGTH_SHORT).show() } } } ftbtn.setOnClickListener{ val intent =Intent(this,MainActivity::class.java) startActivity(intent) } } } ``` # Drawer 在 Android Studio 中使用 Kotlin 創建 Drawer(抽屜)的一般步驟包括: 創建一個新的專案: 開啟 Android Studio,選擇 "Start a new Android Studio project",並按照提示填寫應用程式的名稱、套件名稱等資訊。 選擇 Activity 樣板: 在 "Phone and Tablet" 中選擇 "Empty Activity" 或 "Navigation Drawer Activity",點擊 "Finish"。 設計佈局檔(layout): 在 res/layout 資料夾中找到 activity_main.xml,這是主活動的佈局。通常,使用 DrawerLayout 作為根元素,內含 NavigationView 和主要內容的 FrameLayout。 例如,一個簡單的 activity_main.xml 可能如下所示: ``` <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <!-- 主要內容區域 --> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="?android:attr/actionBarSize" /> <!-- Drawer 抽屜 --> <com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:headerLayout="@layout/nav_header_main" app:menu="@menu/nav_menu" /> </androidx.drawerlayout.widget.DrawerLayout> ``` 設計 Drawer 內容: 在 res/menu 資料夾中建立 nav_menu.xml,定義 Drawer 中的選單項目。 在 res/layout 資料夾中建立 nav_header_main.xml,定義 Drawer 的標頭(可選)。 在 MainActivity 中處理 Drawer: 在 MainActivity.kt 中設置 Drawer 相關的邏輯,包括 Drawer 的開啟、關閉,以及選單項目的點擊事件。 ``` import android.os.Bundle import androidx.appcompat.app.ActionBarDrawerToggle import androidx.drawerlayout.widget.DrawerLayout import com.google.android.material.navigation.NavigationView class MainActivity : AppCompatActivity() { private lateinit var drawerLayout: DrawerLayout private lateinit var navView: NavigationView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) drawerLayout = findViewById(R.id.drawer_layout) navView = findViewById(R.id.nav_view) // 建立 ActionBarDrawerToggle 並設置 DrawerLayout val toggle = ActionBarDrawerToggle( this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) drawerLayout.addDrawerListener(toggle) toggle.syncState() // 處理 NavigationView 中的選單項目點擊事件 navView.setNavigationItemSelectedListener { menuItem -> when (menuItem.itemId) { R.id.nav_item1 -> { // 處理選單項目1的點擊事件 true } R.id.nav_item2 -> { // 處理選單項目2的點擊事件 true } else -> false } } } } ``` 上述程式碼中,ActionBarDrawerToggle 負責與 ActionBar 連動,setNavigationItemSelectedListener 處理 NavigationView 中選單項目的點擊事件。 這是一個簡單的 Drawer 的建立過程。你可以根據自己的需求進一步自定義 Drawer 的內容和行為。 # Splash screen 在Android Studio中使用Kotlin创建一个Splash Screen(启动屏)是相对简单的。以下是一个简单的示例,帮助你创建一个基本的Splash Screen: 打开Android Studio,创建一个新的Kotlin项目。 在res/layout文件夹中创建一个新的XML文件,命名为activity_splash.xml,并添加以下内容: ``` <!-- res/layout/activity_splash.xml --> <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" tools:context=".SplashActivity"> <!-- Add your splash screen content here, such as logo, text, etc. --> </RelativeLayout> ``` 在layout中加入splash screen要顯示之畫面 建立一個splash Activity並設置timer3秒後intent回主畫面 ``` // src/main/java/com/yourpackage/SplashActivity.kt package com.yourpackage import android.content.Intent import android.os.Bundle import android.os.Handler import androidx.appcompat.app.AppCompatActivity class SplashActivity : AppCompatActivity() { private val SPLASH_TIME_OUT: Long = 3000 // 设置启动屏展示时间,单位为毫秒 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) Handler().postDelayed({ // 在延时后启动主Activity val intent = Intent(this, MainActivity::class.java) startActivity(intent) // 关闭当前Activity finish() }, SPLASH_TIME_OUT) } } ``` 修改AndroidManifest.xml文件,把splash Activity設為啟動Activity: ``` <activity android:name=".SplashActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> ``` # Json檔讀取及寫入 先創名為Assets的資料夾並存入json檔 並在MainActivity中 onCreate中寫入read_json() 並創建funtcion read_json() 並定義json為String給予預設值null 在class後先宣告arr及type ``` var arr = arrayListOf<String>() var type = arrListOf<String>() ``` ``` val json :String?=null try{ val inputStream : InputStream = assets.open("json檔檔名") json = inputStream.bufferedReader().use(it.readText()) var jsonarr = JSONArray(json) for(i in 0...jsonarr.length()-1) { var jsonobj = jsonarr.getJSONObject(i) arr.add(jsonobj.getString("name")) //將json檔中的name部分做顯示 type.add(jsonobj.getString("type"))//將json檔中的type部分做顯示 } var adpt= ArrayAdapter(this,android.R.layout.simple_list_item_1,arr) //Apapter(適配器) json_list.adapter =adpt json_list.onItemClickListener = AdapterView.OnItemClickListener(parent, view ,position, id -> Toast.makeText(applicationContext,"Type Slected is" + type[position],toast.length_long).show() //當listview被按下時顯示該陣列中的type } catch(e : IOException){ //json_text為=Listview的id json_text.text = json } ``` # Spinner 在activity中寫入spinner元件 ``` <Spinner android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="wrap_content" tools:listitem="@android:layout/simple_spinner_item" /> ``` 並在onCreate中將選項存入languages中 ``` val languages = arrayOf("英语", "西班牙语", "法语", "德语", "意大利语") ``` 建立一個adapter用來填充Spinner ``` val adapter = ArrayAdapter<String> (this, android.R.layout.simple_spinner_item, languages) ``` 將適配器設為Spinner ``` spinner.adapter = adapter ``` 設置點擊監聽器onSlectedlinster ``` spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { val selectedItem = parent.getItemAtPosition(position) as String Toast.makeText(this@MainActivity, "选择的项目:$selectedItem", Toast.LENGTH_SHORT).show() } override fun onNothingSelected(parent: AdapterView<*>) { // Do nothing } } ``` full code ``` class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val languages = arrayOf("英语", "西班牙语", "法语", "德语", "意大利语") val adapter = ArrayAdapter<String> (this, android.R.layout.simple_spinner_item, languages) spinner.adapter = adapter spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { val selectedItem = parent.getItemAtPosition(position) as String Toast.makeText(this@MainActivity, "选择的项目:$selectedItem", Toast.LENGTH_SHORT).show() } override fun onNothingSelected(parent: AdapterView<*>) { // Do nothing } } } } ``` # 限制EditText的輸入格式(手機號碼) 首先先宣告editText並findviewById到對應物件的id 並設置輸入監聽系統 ``` editText.addTextChanged (object: TextWatcher{ override fun beforeTextChanged (s: CharSequence?, start: Int, count:Int, after :Int){ } override fun onTextChanged (s: CharSequence?,start: Int,before: int, count:Int){ } override funafterTextChanged(s: Editable?) { // 在文本变化之后执行的操作 val phoneNumberPattern = Regex("^09\\d{2}-\\d{3}-\\d{3}$") if (s != null && !s.toString().matches(phoneNumberPattern)) { // 如果不符合指定的手机号格式,显示错误消息 editText.error = "请输入正确的手机号格式(例如:09XX-XXX-XXX)" } else { // 如果符合格式,清除错误消息 editText.error = null } }) ``` # RecyclerView ![螢幕擷取畫面 2024-01-31 160949](https://hackmd.io/_uploads/r1xwgFDqp.png) 先加入recyclerView的implementation ``` implementation ("androidx.recyclerview:recyclerview:1.3.2") implementation ("androidx.recyclerview:recyclerview-selection:1.1.0") ``` 建立dataModel.kt ``` data class Item(val name:String, val img:Int) val myItemList = arrayListOf<item>() for(i in 0..5){ myItemList.add(Item)("Wang,R.drawable.your_img") } ``` 建立一個layout檔 ``` <ImageView android:id"@+id/imgv" android:layout_width="match_parent" android:layout_height="0dp" android:src="@drawable/xxxxxx"/> <TextView android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="30dp" android:text="xxxxxx"/> ``` 創建MyAdapter.kt ``` class MyAdapter:RecyclerView.Adapter<MyAdapter.mViewHolder>() { var unAssignList = listOf<Item>() inner class mViewHolder(itemView:View):RecyclerView.ViewHolder (itemView){ //把layout檔的元件們拉進來,指派給當地變數 val icon = itemView.imgv val name = itemView.tv_name fun bind(item: Item){ //綁定當地變數與dataModel中的每個值 icon.setImageResource(item.img) name01.text = item.name } } override fun onCreateViewHolder(parent:ViewGroup,viewType: Int) :mViewHolder { //載入項目模板 val inflater = LayoutInflater.from(parent.context) val example = inflater.inflate(R.layout.item_example, parent, false) return mViewHolder(example) } override fun getItemCount() = unAssignList.size override fun onBindViewHolder(holder: mViewHolder, position: Int) { //呼叫上面的bind方法來綁定資料 holder.bind(unAssignList[position]) } //更新資料用 fun updateList(list:ArrayList<Movement>){ unAssignList = list } } ``` 在layout中放入recyclerview ``` <androidx.recyclerview.widget.RecyclerView android:id="@+id/r_view" android:layout_width="match_parent" android:layout_height="wrap_content"> </androidx.recyclerview.widget.RecyclerView> ``` ``` class MainActivity : AppCompatActivity() { val mAdapter = MyAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mAdapter.updateList(myItemList) //傳入資料 r_view.layoutManager = LinearLayoutManager(this) r_view.adapter = mAdapter } ``` # viewpager with json 先建立一個view_layout作為模板 我們主要是透過更改view_layout中的textview來實現換頁資訊也跟著換的效果 ![image](https://hackmd.io/_uploads/ryWBeDHTp.png) 接這建立adapter但要注意adapter的類型是recyclerview的 接者照recyclerview adapter的格式 ``` class MyAdapter(private val dataList: List<String>, private val context: Context) : RecyclerView.Adapter<MyAdapter.ViewHolder>() { // 创建 ViewHolder,用于保存视图引用 class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val textView: TextView = itemView.findViewById(R.id.textView) } // 创建新的 ViewHolder 实例 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false) return ViewHolder(view) } // 将数据绑定到 ViewHolder 上 override fun onBindViewHolder(holder: ViewHolder, position: Int) { val data = dataList[position] holder.textView.text = data } // 返回数据集的大小 override fun getItemCount(): Int { return dataList.size } } ``` 需修改的地方為datafile的地方改成data class的最外層 onCreatViewHolder的layoutInflater中的layout改成view_layout getItemCount內放data class中的第一個list的size並return 需而外創建inner class ViewHolder ``` inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val textView1: TextView = itemView.findViewById(R.id.where) val textView2: TextView=itemView.findViewById(R.id.feel) val textView3: TextView=itemView.findViewById(R.id.max) val textView4: TextView=itemView.findViewById(R.id.min) val textView5: TextView =itemView.findViewById(R.id.Start) val textView6: TextView=itemView.findViewById(R.id.End) } ``` 裡面要放你要做變更的物件(要findviewbyid) 在onbindViewHolder加入holder.apply 放入物件要更改成的東西 並在要做讓陣列中會做變更的數字換成position ## 主頁面 ``` class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val jsonfile = assets.open("Weather.json").bufferedReader().use { it.readText() } val datafile = Gson().fromJson(jsonfile, weather::class.java) Log.d("aha", datafile.cwaopendata.dataset.location[0].locationName!!) val viewPager = findViewById<ViewPager2>(R.id.viewpager) viewPager.adapter = MyPagerAdapter(this, datafile) } } ``` # Map套件 先在layout畫面中放置一個Mapview套件 並定義mapView(findviewByid) 设置应用程序的用户代理值 Configuration.getInstance().userAgentValue = "your_app_name" 設置一個ConnectView到Activity_main // 设置地图图层为 MAPNIK mapView.setTileSource(TileSourceFactory.MAPNIK) // 设置初始的经纬度坐标 val initialLatLong = GeoPoint(37.7749, -122.4194) // 将地图中心设置为初始经纬度 mapView.controller.setCenter(initialLatLong) // 设置初始缩放级别 mapView.controller.setZoom(12.0) # Room ## 定義data class ``` import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, val age: Int ) ``` ## 建立Dao ``` import androidx.room.Dao import androidx.room.Insert import androidx.room.Query @Dao interface UserDao { @Query("SELECT * FROM users") fun getAll(): List<User> @Insert fun insert(user: User) @Query("DELETE FROM users") fun deleteAll() } ``` ## 建立資料庫 ``` import androidx.room.Database import androidx.room.RoomDatabase @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } ``` ## 初始化 ``` import androidx.room.Room import android.content.Context class MyApp : Application() { companion object { lateinit var database: AppDatabase } override fun onCreate() { super.onCreate() database = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "my-database" ).build() } } ``` ## 使用 ``` val userDao = MyApp.database.userDao() // 插入用户 val user = User(1, "John", 30) userDao.insert(user) // 获取所有用户 val users = userDao.getAll() ``` # xml解析 先建立一個Class 並宣告其形態 ``` Class horly(hous:String, temp :int,picpath:String){ privare var hours:String="" private var temp:int =0 private var pucpath: String="" init{ this.hours=hours this.temp=temp this.picpath=picpath } fun getHours():String{ return hours } fun sethours(hours:String){ this.hours=hours } } 以此類推 ``` 並在mainActivity寫入 ``` fun parserXal(): ArrayList<Hourly> { val items: ArrayList<Hourly> = ArrayList() val parser = resources.getXml(R.xml.weather_xml) var eventType = parser.eventType var myIndex = -1 while (eventType != XmlResourceParser.END_DOCUMENT) { if (eventType == XmlResourceParser.START_TAG) { val tagName = parser.name when (tagName) { "element" -> { items.add(Hourly("", 0, "")) myIndex++ } "temp" -> { parser.next() items[myIndex].setTemp(parser.text.toInt()) parser.next() } "hours" -> { parser.next() items[myIndex].setHours(parser.text) parser.next() } "pic" -> { parser.next() items[myIndex].setPicPath(parser.text) parser.next() } } } eventType = parser.next() } return items } ``` ## 在recyclerview中的範例 ``` import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import coil.load import com.example.xnlparser.domain.Hourly import com.example.xml.parser.R class HourlyAdapter(private var items: ArrayList<Hourly>) : RecyclerView.Adapter<HourlyAdapter.ViewHolder>() { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val hourtext: TextView = view.findViewById(R.id.hourtext) val image: ImageView = view.findViewById(R.id.imageView) val degrees: TextView = view.findViewById(R.id.degrees) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val v = LayoutInflater.from(parent.context).inflate(R.layout.viewholder_hourly, parent, false) return ViewHolder(v) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val data = items.get(position) holder.hourtext.text = data.getHours() holder.degrees.text = data.getTemp().toString() + "°" val drawableId = holder.itemView.resources.getIdentifier(data.getPicPath(), "drawable", holder.itemView.context.packageName) holder.image.load(drawableId) } override fun getItemCount(): Int { return items.size } } ``` ``` import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.osmdroid.config.Configuration import org.osmdroid.util.GeoPoint import org.osmdroid.views.MapView import org.osmdroid.tileprovider.tilesource.TileSourceFactory class MainActivity : AppCompatActivity() { private lateinit var mapView: MapView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Configuration.getInstance().userAgentValue = "yourapp_name" setContentView(R.layout.activity_main) mapView = findViewById(R.id.mapView) mapView.setTileSource(TileSourceFactory.MAPNIK) val fileName = "taiwanEMap6.mbtiles" val file = File(getExternalFilesDir(null), fileName) file.outputStream().use { assets.open(fileName).copyTo(it) } val tileProvider = OfflineTileProvider( SimpleRegisterReceiver(this@Mappage), arrayOf(file) ) mapId.tileProvider = tileProvider val initialLatLong = GeoPoint(37.7749, -122.4194) mapView.controller.setCenter(initialLatLong) mapView.controller.setZoom(12.0) } override fun onDestroy() { super.onDestroy() mapView.onDetach() } } ```