【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>
逻辑实现
数据存储:
因为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属性。