Android手機應用開發(七) | 資料儲存(下)
實驗目的
- 學習SQLite資料庫的使用。
- 學習ContentProvider的使用。
- 複習Android介面程式設計。
這次大概是做一個有登入、註冊、評論、點贊等功能的小型APP
效果如下:(圖片比較大)
登入註冊頁面的切換
兩個按鈕用RadioButton
就可以實現了
<RadioGroup
android:id="@+id/radio_group"
android:layout_marginTop="120dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/login_button"
android:text="Login"
android:checked="true"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<RadioButton
android:id="@+id/register_button"
android:text="Register"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</RadioGroup>
但是,如何實現兩種情況下顯示不同佈局,並且兩個按鈕都剛好在填寫的資訊底下呢
這裡可以用設定控制元件的Visibility
實現的!
我把按鈕上方的所有東西放在一個View
裡面(也就是登入介面的兩個輸入框,註冊頁面的三個輸入框,一個頭像顯示框)
通過ConstraintLayout
的特點讓兩個按鈕固定在這個大的View裡面,登入狀態或者註冊狀態讓頭像框和確認密框隱藏就好了
其實還有另一種辦法:
只需要用四個控制元件(複用兩個輸入框)
登入頁面的頭像框隱藏,確認密碼欄隱藏, 然後在註冊頁面時顯示
但是會有挺多問題(感覺是),因為註冊和登入用同樣的輸入框會讓後期事件處理以及顯示
HINT
的操作有點繁瑣
這就是那個大的View
,包含了6個控制元件
<android.support.constraint.ConstraintLayout
android:id="@+id/edit_area"
app:layout_constraintTop_toBottomOf="@id/radio_group"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/login_username"
android:layout_width="match_parent"
android:layout_height="40dp"
android:hint="Username"/>
<EditText
android:id="@+id/login_password"
android:layout_width="match_parent"
android:inputType="textPassword"
android:layout_height="40dp"
android:hint="Password"
app:layout_constraintTop_toBottomOf="@id/login_username"/>
<ImageButton
android:visibility="gone"
android:src="@mipmap/add"
android:id="@+id/register_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitXY"
android:padding="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<EditText
android:visibility="gone"
android:id="@+id/register_username"
android:layout_width="match_parent"
android:layout_height="40dp"
android:hint="Username"
app:layout_constraintTop_toBottomOf="@id/register_image"/>
<EditText
android:visibility="gone"
android:inputType="textPassword"
android:id="@+id/register_password"
android:layout_width="match_parent"
android:layout_height="40dp"
android:hint="New Password"
app:layout_constraintTop_toBottomOf="@id/register_username"/>
<EditText
android:visibility="gone"
android:id="@+id/register_confirm_password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="40dp"
android:hint="Confirm Password"
app:layout_constraintTop_toBottomOf="@id/register_password"/>
</android.support.constraint.ConstraintLayout>
這是底下的兩個按鈕
<Button
android:id="@+id/button_ok"
android:layout_width="90dp"
android:layout_height="50dp"
android:text="OK"
app:layout_constraintTop_toBottomOf="@id/edit_area"/>
<Button
android:id="@+id/button_clear"
android:layout_width="90dp"
android:layout_height="50dp"
android:text="CLEAR"
android:layout_marginStart="30dp"
app:layout_constraintStart_toEndOf="@id/button_ok"
app:layout_constraintTop_toBottomOf="@id/edit_area"/>
然後在JAVA
程式碼裡切換VISIBILITY
屬性
final RadioGroup radioGroup = findViewById(R.id.radio_group);
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if(((RadioButton)findViewById(checkedId)).getText().toString().equals("Login")){
register_confirm_password.setVisibility(View.GONE);
register_image.setVisibility(View.GONE);
register_password.setVisibility(View.GONE);
register_username.setVisibility(View.GONE);
login_username.setVisibility(View.VISIBLE);
login_password.setVisibility(View.VISIBLE);
status = true;
}else{
register_confirm_password.setVisibility(View.VISIBLE);
register_image.setVisibility(View.VISIBLE);
register_password.setVisibility(View.VISIBLE);
register_username.setVisibility(View.VISIBLE);
login_username.setVisibility(View.GONE);
login_password.setVisibility(View.GONE);
status = false;
}
}
});
注意:這裡設定為
GONE
而不是INVISIBLE
,因為INVISIBLE
雖然不顯示但是佔用空間,而GONE
不會佔用空間
SQLite的簡單使用
這裡建立一個SQLiteHelper
用來儲存使用者名稱,密碼,和頭像
package com.example.myapplication;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
public class UserSQLiteHelper extends SQLiteOpenHelper {
private static final String DB_NAME= "db_project3";
private static final String TABLE_NAME = "user";
private static final int DB_VERSION = 1;
private static final String[] COLUMNS = {"username","password","image"};
public UserSQLiteHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
String CREATE_TABLE = "CREATE TABLE if not exists "
+ TABLE_NAME
+ " ( username TEXT PRIMARY KEY, password TEXT, image TEXT)";
sqLiteDatabase.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int ii) {
}
//新增操作
public Boolean addUser(String name, String password, String image) {
SQLiteDatabase db = getWritableDatabase();
Cursor cursor =
db.query(TABLE_NAME, // a. table
COLUMNS, // b. column names
" username = ?", // c. selections
new String[] { name }, // d. selections args
null, // e. group by
null, // f. having
null, // g. order by
null); // h. limit
// 3. if we got results get the first one
if (cursor.moveToFirst()){
db.close();
return false;
}
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password);
cv.put("image", image);
db.insert(TABLE_NAME, null, cv);
db.close();
return true;
}
//驗證登入
public int loginVerify(String name, String password){
SQLiteDatabase db = getWritableDatabase();
Cursor cursor =
db.query(TABLE_NAME, // a. table
COLUMNS, // b. column names
" username = ?", // c. selections
new String[] { name }, // d. selections args
null, // e. group by
null, // f. having
null, // g. order by
null); // h. limit
if (!cursor.moveToFirst()){
db.close();
return 1;//username not found
}
if(!password.equals(cursor.getString(1))){
db.close();
return 2;//user password not correct
}
db.close();
return 0;
}
//得到使用者頭像
public String getImage(String name){
SQLiteDatabase db = getWritableDatabase();
Cursor cursor =
db.query(TABLE_NAME, // a. table
COLUMNS, // b. column names
" username = ?", // c. selections
new String[] { name }, // d. selections args
null, // e. group by
null, // f. having
null, // g. order by
null); // h. limit
if (!cursor.moveToFirst()){
db.close();
return null;//username not found
}else {
String image = cursor.getString(2);
cursor.close();
db.close();
return image;
}
}
//刪除使用者表
public void dropUser(){
//刪除表的SQL語句
String sql ="DROP TABLE user";
//執行SQL
this.getWritableDatabase().execSQL(sql);
}
}
在Activity
的onCreate
方法中new一個SQLiteHelper
的例項
userSQLiteHelper = new UserSQLiteHelper(this);
然後在註冊頁面的button_ok
的監聽事件裡呼叫新增使用者的方法就實現了註冊功能
if(userSQLiteHelper.addUser(register_username.getText().toString(), register_password.getText().toString(), default_image)){
Toast.makeText(MainActivity.this, "Register Successfully.",Toast.LENGTH_SHORT).show();
return;
在登入頁面的button_ok的監聽事件裡呼叫驗證使用者的方法就實現了登入功能
switch (userSQLiteHelper.loginVerify(login_username.getText().toString(), login_password.getText().toString())){
case 0:
//跳轉到評論頁面
Toast.makeText(MainActivity.this, "Login Successfully.",Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this, CommentActivity.class);
intent.putExtra("username", login_username.getText().toString());
intent.putExtra("image",userSQLiteHelper.getImage(login_username.getText().toString()));
startActivity(intent);
break;
case 1:
Toast.makeText(MainActivity.this, "Username not existed.",Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(MainActivity.this, "Invalid Password.",Toast.LENGTH_SHORT).show();
break;
}
呼叫系統相簿選擇圖片
這裡的使用者頭像其實是一個ImageButton
,設定監聽事件為
String default_image = "android.resource://com.example.myapplication/" + R.mipmap.me;
register_image = findViewById(R.id.register_image);
register_image.setImageURI(Uri.parse(default_image));
register_image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, 0);
}
});
這樣通過呼叫Intent
打開了系統的圖片檢視器,然後還是用onActivityResult
方法接收點選的圖片
SQLite中儲存圖片
Android
資料庫中存取圖片通常使用兩種方式
- 儲存圖片所在路徑
- 將圖片以二進位制的形式儲存(
sqlite3
支援BLOB
資料型別)
從我之前的程式碼中可以看出我是通過儲存圖片的Uri
儲存頭像,以後顯示該頭像就用該Uri
做引數就好了
Android
中的URI
和Uri
是不一樣的,從import
的包就可以看出來import java.net.URI;
import android.net.Uri;
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) {
// 得到圖片的全路徑
Uri uri = data.getData();
//圖片預覽
this.register_image.setImageURI(uri);
//儲存該URI
default_image = image_file_uri.toString();
}
super.onActivityResult(requestCode, resultCode, data);
}
注:這裡的
default_image
儲存的是預設使用者頭像,如果選擇了自定義的使用者頭像它也會改成新的頭像,因為最後新增頭像的時候是呼叫它
但是!!!這樣是有問題的
就是剛新增的時候圖片能成功顯示,但是關閉APP重新啟動或者放在後臺又返回的話圖片就顯示不了了
會出現如下警告
W/ImageView: Unable to open content: content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F6217/NO_TRANSFORM/1633586863
java.lang.SecurityException: Permission Denial: opening provider com.google.android.apps.photos.contentprovider.MediaContentProvider from ProcessRecord{2e6acd5 26947:com.example.myapplication/u0a67} (pid=26947, uid=10067) that is not exported from uid 10030
可以看到是因為沒有許可權訪問這個圖片,應該是開啟選擇圖片視窗的Intent
是一次性的,即使儲存了它的路徑,以後想得到它作業系統也不允許,為了解決這個問題,而且又不想學習BLOB
的使用,最簡單的辦法
把圖片存到