【Android】登陸界面設計


界面布局

布局其實很簡單,用相對布局累起來就可以了,然后注冊和記住密碼這兩個控件放在一個水平線性布局里

界面底部還設置了一個QQ一鍵登錄的入口,可以直接用。

控件的ID命名有點亂

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="100dp"
    android:paddingLeft="20dp"
    android:paddingRight="20dp"
    android:background="@drawable/im_background">
    <ImageView
        android:id="@+id/iv_1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerHorizontal="true"
        android:scaleType="fitCenter"
        android:src="@drawable/image_boy"/>
    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:lines="1"
        android:layout_marginTop="10dp"
        android:hint="@string/hint_username"
        android:textColorHint="@color/app_white"
        android:textColor="@color/app_white"
        android:layout_below="@id/iv_1"/>
    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:lines="1"
        android:hint="@string/hint_password"
        android:inputType="textPassword"
        android:layout_marginTop="10dp"
        android:layout_below="@id/et_username"
        android:textColor="@color/app_white"
        android:textColorHint="@color/app_white"/>
    <LinearLayout
        android:id="@+id/ly_1"
        android:layout_below="@id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <CheckBox
            android:id="@+id/cb_rm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/et_password"
            android:text="@string/checkbox_re"
            android:textColor="@color/app_white"
            android:layout_gravity="left"
            android:layout_weight="3"/>

        <TextView
            android:id="@+id/btn_register"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="right"
            android:gravity="center"
            android:text="@string/button_cancel"
            android:textColor="@color/app_white"
            android:textSize="14sp"
            android:layout_weight="1"/>
    </LinearLayout>

    <Button
        android:id="@+id/btn_confirm"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/ly_1"
        android:layout_marginTop="35dp"
        android:background="@drawable/btn_bg_red"
        android:text="@string/button_confirm"
        android:layout_centerHorizontal="true"
        android:textColor="@color/app_white" />


<ImageButton
    android:id="@+id/im_qq"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:src="@drawable/qq"
    android:scaleType="fitCenter"/>

</RelativeLayout>

注意一下我在styles中把所有框顏色改成了白色(為了更好適配黑色背景),你也可以選擇不改

<resources>

    <!-- Base application theme. -->
        <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
            <item name="colorAccent">@color/colorPrimary</item>
            <!-- AppCompatEditText默認狀態狀態設置底線顏色 -->
            <item name="colorControlNormal">#FFFFFF</item>
            <!-- AppCompatEditText選擇的底線顏色 -->
            <item name="colorControlActivated">#FFFFFF</item>
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    </style>

</resources>
styles.xml

邏輯實現

數據存儲:

因為map正好key與value一一對應(即name與password一一對應),所以選擇使用map來進行賬號的保存。

而本地的存儲是用的SharedPreference來實現的,但是由於SharedPreference無法直接對map進行存儲,所以需要單獨寫一個方法來實現。

密碼是用Base64做了一次轉碼操作,增強了一丟丟的安全性。

import android.content.Context;
import android.content.SearchRecentSuggestionsProvider;
import android.content.SharedPreferences;
import android.util.Base64;

import com.paul.notebook.LoginActivity;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 作者:created by 巴塞羅那的余暉 on 2019/3/10 18:45
 * 郵箱:zhubaoluo@outlook.com
 * 不會寫BUG的程序猿不是好程序猿,嚶嚶嚶
 */
public class userData {
    private Map<String,String> root;//用戶數據根目錄
    private static String flag="user_data";//標識
    private  boolean loginState=false;
    private Context mcontext;
    public userData(Context context)
    {
        root=new HashMap<>();
        mcontext=context;
        readData(flag);
    }
    public boolean findExistUsername(String name)//查找是否有該用戶名
    {
        return root.containsKey(name);
    }
    public boolean verifyPassword(String name,String password)//驗證用戶名和密碼是否匹配
    {
        if(findExistUsername(name))
        {
            if(root.get(name).equals(password))
            {
                return true;
            }
        }
        return false;
    }
    public boolean getRegister(String name,String password)//注冊,並返回是否注冊成功
    {
        if(findExistUsername(name)||name.equals("")||password.equals(""))
        {
            return false;
        }
        root.put(name,password);
        saveData(flag);
        return true;
    }
    private void saveData(String key)//保存數據到本地
    {
        //原理:將Map格式轉換成json字符串,並指定一個特定標識來記錄,Value按照發生器逐一保存就好
        JSONArray mJsonArray = new JSONArray();
        Iterator<Map.Entry<String, String>> iterator = root.entrySet().iterator();
        JSONObject object = new JSONObject();

        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            try {
                String password_encode=entry.getValue();
                password_encode=Base64.encodeToString(password_encode.getBytes(),Base64.NO_WRAP);
                object.put(entry.getKey(), password_encode);

            } catch (JSONException e) {
                //異常處理
                //媽咪媽咪哄!BUG快離開!
            }
        }
        mJsonArray.put(object);
        SharedPreferences sp=mcontext.getSharedPreferences("config",Context.MODE_PRIVATE);
        SharedPreferences.Editor editor=sp.edit();
        editor.putString(key,mJsonArray.toString());
        editor.commit();
    }
     private void readData(String key)//sharedpreferences從本地讀取數據
    {
        root.clear();
        SharedPreferences sp=mcontext.getSharedPreferences("config",Context.MODE_PRIVATE);
        String result=sp.getString(key,"");
        try {
            JSONArray array=new JSONArray(result);
            for(int i=0;i<array.length();i++)
            {
                JSONObject itemObject =array.getJSONObject(i);
                JSONArray names=itemObject.names();
                if(names!=null)
                {
                    for(int j=0;j<names.length();j++)
                    {
                        String name=names.getString(j);
                        String value=itemObject.getString(name);
                        value=new String(Base64.decode(value.getBytes(),Base64.NO_WRAP));
                        root.put(name,value);
                    }
                }
            }
        }catch (JSONException e){
            //異常處理
            //媽咪媽咪哄!BUG快離開!
        }
    }
    private void clearLocalData()//清除本地數據,這個方法一般用不到,故寫成私有的
    {
        SharedPreferences preferences = mcontext.getSharedPreferences("config", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();
        editor.clear();
        editor.commit();
    }

}

主代碼:

由於有一個保存密碼功能,所以需要單獨再使用SharedPreference來存儲記住密碼的賬號啥的。同樣也是使用Base64進行轉碼操作。

import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.paul.notebook.Tools.Base64Activity;
import com.paul.notebook.dataBase.userData;

/**
 * 作者:created by 巴塞羅那的余暉 on 2019/3/10 18:35
 * 郵箱:zhubaoluo@outlook.com
 * 不會寫BUG的程序猿不是好程序猿,嚶嚶嚶
 */
public class LoginActivity extends AppCompatActivity {
    //聲明變量
    private Boolean login_state=false;
    private TextView mregister;
    private ImageView mimageView;
    private ImageButton mimageButton;
    private EditText muesername, mpassword;
    private Button mconfirm;
    private userData data;
    private CheckBox checkBox;
    private String LOCAL_USER_NAME="paul";
    private String portraitURL = "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2281052020,3958255485&fm=27&gp=0.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        //找布局控件對應ID
        mimageView = findViewById(R.id.iv_1);
        mimageButton = findViewById(R.id.im_qq);
        muesername = findViewById(R.id.et_username);
        mpassword = findViewById(R.id.et_password);
        mconfirm = findViewById(R.id.btn_confirm);
        mregister = findViewById(R.id.btn_register);
        checkBox=findViewById(R.id.cb_rm);
        data=new userData(LoginActivity.this);
        //Glide.with(LoginActivity.this).load(portraitURL).into(mimageView);
        initLogin();
        mregister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                {
                    if(data.getRegister(muesername.getText().toString(),mpassword.getText().toString()))
                    {
                        Toast.makeText(LoginActivity.this,"注冊成功!",Toast.LENGTH_SHORT).show();
                    }
                    else
                    {
                        Toast.makeText(LoginActivity.this,"注冊失敗!用戶名重復或者為空!",Toast.LENGTH_SHORT).show();
                    }
                }


            }
        });
        mconfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(data.verifyPassword(muesername.getText().toString(),mpassword.getText().toString()))
                {
                    Intent intent=new Intent(LoginActivity.this, Base64Activity.class);
                    saveLogin(login_state);
                    startActivity(intent);
                    //Toast.makeText(LoginActivity.this,muesername.getText().toString()+"歡迎您!",Toast.LENGTH_SHORT).show();
                }
                else
                {
                    Toast.makeText(LoginActivity.this,"登陸失敗!請檢查用戶是否存在或者密碼錯誤!",Toast.LENGTH_SHORT).show();
                }
            }
        });
        checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                login_state=isChecked;
            }
        });
    }
    private void saveLogin(boolean flag)
    {
        SharedPreferences sharedPreferences = LoginActivity.this.getSharedPreferences("ACCOUNT_REMEMBER", MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        String secreat_name = muesername.getText().toString();
        String secreat_password=mpassword.getText().toString();
        if(sharedPreferences.getBoolean("flag",false))
        {
            return;
            //驗證通過,不需要再次保存了
        }
        if(flag) {
            secreat_password=Base64.encodeToString(secreat_password.getBytes(),Base64.NO_WRAP);
            editor.putString("name",secreat_name);
            editor.putString("password", secreat_password);
            editor.putBoolean("flag", flag);
            editor.commit();
        }
        else{
            editor.clear();
            editor.putString("name",secreat_name);
            editor.putBoolean("flag",false);
            editor.commit();
        }

    }
    private void cleanState()
    {
        SharedPreferences sharedPreferences = LoginActivity.this.getSharedPreferences("ACCOUNT_REMEMBER", MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.clear();
        editor.commit();
    }
    private void initLogin()
    {
        SharedPreferences sharedPreferences=LoginActivity.this.getSharedPreferences("ACCOUNT_REMEMBER", MODE_PRIVATE);
        if(sharedPreferences.getBoolean("flag",false)){
            String decode_password=sharedPreferences.getString("password","");
            decode_password= new String(Base64.decode(decode_password.getBytes(),Base64.NO_WRAP));
            muesername.setText(sharedPreferences.getString("name",""));
            mpassword.setText(decode_password);
            checkBox.setChecked(true);
        }
        else {
            muesername.setText(sharedPreferences.getString("name",""));
            checkBox.setChecked(false);
        }
    }
}

Base64測試

當時考慮到安全性問題所以就寫了這個來測試Base64的加密解密,但是加密后無法通過解密獲得原文,超級煩!!!

下面列出兩個我遇到的(應該大部分就是這兩個)問題。

1.字符串轉換問題

當Base64解密的時候返回的是一個byte[],肯定需要轉成字符串,所以我就直接使用了toString方法,結果發現轉換出來的是@開頭的一堆亂碼。

在網上查閱資料后發現轉換String有兩種方法,一種是直接toString,還有一種是new String

toString()與new String ()用法區別

str.toString是調用了b這個object對象的類的toString方法。一般是返回這么一個String:[class name]@[hashCode]。
new String(str)是根據parameter是一個字節數組,使用java虛擬機默認的編碼格式,將這個字節數組decode為對應的字符。若虛擬機默認的編碼格式是ISO-8859-1,按照ascii編碼表即可得到字節對應的字符。

什么時候用什么方法呢?

new String()一般使用字符轉碼的時候,byte[]數組的時候
toString()將對象打印的時候使用 
--------------------- 
作者:火星碼農 
來源:CSDN 
原文:https://blog.csdn.net/u014622411/article/details/45147363 
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

2.編碼方式選擇問題

在網上看到的Base64基本使用方法都是直接傳Base64.DEFAULT,使用默認編碼方式來進行編碼。看似沒有問題,但是!!!如果字符串過長,比如80位,那么在轉碼的過程中會自動添加換行符,如果是和別的模塊進行對接就會出現錯誤···

所以我們一般就強制設置成忽略所有換行符就OK了

具體五種參數的含義:

CRLF:這個參數看起來比較眼熟,它就是Win風格的換行符,意思就是使用CR LF這一對作為一行的結尾而不是Unix風格的LF

DEFAULT:這個參數是默認,使用默認的方法來編碼

NO_PADDING:這個參數是略去編碼字符串最后的“=”

NO_WRAP:這個參數意思是略去所有的換行符(設置后CRLF就沒用了)

URL_SAFE:這個參數意思是編碼時不使用對URL和文件名有特殊意義的字符來作為編碼字符,具體就是以-和_取代+和/
--------------------- 
原文:https://blog.csdn.net/z191726501/article/details/52778478 

3.使用方法

//加密
 String Econtent=“我是原文”;//自己定義一個字符串就好
 Econtent= Base64.encodeToString(Econtent.getBytes(),Base64.NO_WRAP);
//解密
 String Dcontent=“我是密文”;
Dcontent=new String(Base64.decode(Dcontent.getBytes(),Base64.NO_WRAP));//在這里特別注意一下使用new String就行

 

最終效果圖(僅供參考,還是很丑的···)

背景、頭像和QQ一鍵登錄圖片自己去到網上找一下就可以了,注意一下大小什么的,雖然用了fit_center屬性。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM