1. 程式人生 > >Android手機應用開發(七) | 資料儲存(下)

Android手機應用開發(七) | 資料儲存(下)

實驗目的

  1. 學習SQLite資料庫的使用。
  2. 學習ContentProvider的使用。
  3. 複習Android介面程式設計。

這次大概是做一個有登入、註冊、評論、點贊等功能的小型APP

效果如下:(圖片比較大)

GIF

登入註冊頁面的切換

1

兩個按鈕用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);
    }
}

ActivityonCreate方法中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;
}

呼叫系統相簿選擇圖片

2

這裡的使用者頭像其實是一個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中的URIUri是不一樣的,從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的使用,最簡單的辦法

把圖片存到