界面布局
布局其實很簡單,用相對布局累起來就可以了,然后注冊和記住密碼這兩個控件放在一個水平線性布局里
界面底部還設置了一個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>
邏輯實現
數據存儲:
因為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屬性。