第3章 登 录 模 块 本章学习目标 掌握登录模块的库表设计。 了解登录模块的功能。 掌握登录模块服务端的功能实现。 掌握Android App登录页面的开发。 登录模块是整个系统的门户,所有操作都需要先登录系统才得以进行。登录模块的信息验证中不仅涉及用户名和密码的验证,还为登录成功的用户匹配唯一的token(密钥),方便在后续章节中实现权限的验证功能。本章将带领读者从创建数据表开始正式学习登录模块的开发,以及接口的测试。 3.1登录表设计 3.1.1设计表结构 通常一个公司的用户表包含的字段很多,例如,性别、年龄和籍贯等,但员工表必需的字段有用户名和密码。此处为了项目演示方便,暂定四个字段,分别是fuseraccount(员工工号)、fname(员工姓名)、mobilepassword(登录密码),以及dapaderment_id(员工部门)。 3.1.2创建数据表 创建用户表的SQL语句如下: 1create table T_SEC_USER( 2id int not null identity(1,1) primary key, 3fuseraccount nvarchar(20),--员工工号 4fname nvarchar(20),--员工姓名 5mobilepassword varchar(50),--登录密码 6dapaderment_id int--员工部门 7 ) 3.1.3用户登录、修改密码功能接口 在SqlserverGenerator类中指定需要生成的数据表名>strategy.setInclude("T_SEC_USER"); 运行SqlserverGenerator类中的main()方法,此时控制面板中将打印如图3.1所示的提示信息。 图3.1使用MyBatisPlus生成代码 第 3 章 登录模块 Java移动端企业大型项目实战SpringBoot+Android 在项目的目录下可以发现,图3.1中的文件都已悉数生成。在项目目录下新建utils文件夹,创建RedisOps工具类,连接Redis数据库,并实现存取数据操作,代码如下所示。 1public class RedisOps { 2public static void set(String key,String value){ 3Jedis jedis = new Jedis ("localhost",6379); 4jedis.set(key, value); 5jedis.close(); 6} 7public static String get(String key){ 8Jedis jedis = new Jedis ("localhost",6379); 9String value = jedis.get(key); 10jedis.close(); 11return value; 12} 13public static void setObject(String key,Object object){ 14Jedis jedis = new Jedis ("localhost",6379); 15jedis.set(key.getBytes(), SerializeUtil.serizlize(object)); 16jedis.close(); 17} 18public static Object getObject(String key){ 19Jedis jedis = new Jedis ("localhost",6379); 20byte[] bytes = jedis.get(key.getBytes()); 21jedis.close(); 22if(bytes == null) return null; 23return SerializeUtil.deserialize(bytes); 24} 25} 在以上代码中,导入Jedis包,分别实现了String类型和Object类型的数据在Redis中的存取功能。需要注意的是,在存取Object类型时,需要进行字节转换。 1. 编辑TSecUserMapper.xml文件 在XML文件中添加查询用户信息和修改密码的语句,代码如下。 1<!-- 登录 --> 2<select id="getLogin" resultMap="getLogin" 3parameterType="java.lang.String"> 4SELECT fuseraccount, mobilepassword, fname FROM T_SEC_USER where 5fuseraccount=#{fuseraccount} 6</select> 7<!-- 修改密码 --> 8<update id="updatePassword"> 9UPDATE T_SEC_user 10SET MOBILEPASSWORD=#{mobilepassword} 11WHERE FUSERACCOUNT=#{fuseraccount} 12</update> 2. 编辑TSecUserMapper接口文件 1@Mapper 2public interface TSecUserMapper extends BaseMapper<TSecUser> { 3//登录 4User getLogin(@Param("fuseraccount") String fuseraccount); 5//修改密码 6int updatePassword(@Param("fuseraccount") String fuseraccount, 7@Param("mobilepassword") String mobilepassword); 8} 3. 编写TSecUserService接口 1public interface TSecUserService extends IService<TSecUser> { 2//用户登录 3User getLogin(@Param("fuseraccount") String fuseraccount); 4//修改密码 5int updatePassword(@Param("fuseraccount") String fuseraccount, 6@Param("mobilepassword") String mobilepassword); 7} 4. 编辑TSecUserServiceImpl 实现类 1@Service 2public class TSecUserServiceImpl extends ServiceImpl<TSecUserMapper, 3TSecUser> implements TSecUserService { 4//用户登录 5@Override 6public User getLogin(String fuseraccount) { 7return baseMapper.getLogin(fuseraccount); 8} 9//修改密码 10@Override 11public int updatePassword(String fuseraccount, String mobilepassword) 12{ 13return baseMapper.updatePassword(fuseraccount,mobilepassword); 14} 15} 16} 5. 编辑TUserController 1@RestController 2@RequestMapping("/user") 3public class TUserController { 4@Resource 5private TSecUserService tSecUserService; 6//登录 7@PostMapping("getLogin") 8public Response getLogin(String username, String password) { 9//对密码进行 md5 加密 10Jedis jedis = new Jedis ("localhost",6379); 11String pwd = DigestUtils.md5DigestAsHex(password.getBytes()); 12Response<User> response = new Response<>(); 13User user = tSecUserService.getLogin(username); 14if (user == null) { 15response.setCode(-1); 16response.setMessage("用户不存在"); 17return response; 18} 19if (!user.getMobilepassword().equals(pwd)) { 20response.setCode(-1); 21response.setMessage("密码错误"); 22return response; 23} else { 24List<RoleAndPower> list1 = 25getRolesAndPowersService.getRolesAndPowers(username); 26List<String> roles = tSecUserService.getRoles(username); 27user.setRoles(roles); 28String token = UUID.randomUUID().toString().replaceAll("-", ""); 29 30jedis.set(("token_"+token).getBytes(), 31SerializeUtil.serizlize(list1)); 32jedis.expire(("token_"+token),60*30*2); 33System.out.println("token_"+token); 34System.out.println(RedisOps.getObject("token_"+token)); 35response.setCode(200); 36response.setHeader("token_"+token); 37response.setResult(user); 38return response; 39} 40} 41@PutMapping("updatePassword") 42@ResponseBody 43public Response updatePassword(String fuseraccount, String 44mobilepassword) { 45TSecUser tSecUser = tSecUserService.queryByCount(fuseraccount); 46Response<List<String>> response = new Response<>(); 47if (tSecUser == null) { 48response.setCode(-1); 49response.setMessage("账户不存在"); 50return response; 51} else { 52//对密码进行 md5 加密 53String pwd = 54DigestUtils.md5DigestAsHex(mobilepassword.getBytes()); 55int i = tSecUserService.updatePassword(fuseraccount, pwd); 56if (i == 0) { 57response.setCode(-1); 58response.setMessage("密码修改失败"); 59return response; 60} else { 61response.setCode(200); 62response.setMessage("密码修改成功"); 63return response; 64} 65} 66} 67} 以上代码中通过用户名和密码实现登录功能,若用户输入的用户名和密码正确则随机生成token字符串,将其保存在Redis缓存中,通过expire()方法设置token字符串在Redis中的存储时长,最后将用户信息和token返回给客户端,该token主要用作一些增、删、改操作时的权限验证,后续的章节中将逐步讲解。 3.1.4用户登录、修改密码功能接口测试 1. 用户登录接口测试 在postman地址栏中输入登录测试接口地址,调用登录接口,如图3.2所示。 图3.2登录接口测试 2. 修改密码接口测试 在postman地址栏中输入修改密码测试接口地址,调用登录接口,如图3.3所示。 图3.3修改密码接口测试 3.2实现登录功能 3.2.1用户登录与记住密码 在实际项目开发过程中,登录功能通常是必不可少的,此功能主要包含用户登录、修改用户密码和记住用户密码。接下来通过讲解关键代码,使读者理解与掌握登录功能的实现过程。 1. 编写登录View 编写代码,实现效果如图1.4所示。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width="wrap_content" android: layout_height="match_parent" android: background="#ffffff" android: filterTouchesWhenObscured="true" android: orientation="vertical"> <RelativeLayout android: id="@+id/login_layout" android: layout_width="wrap_content" android: layout_height="658dp" android: layout_marginLeft="20dp" android: layout_marginRight="20dp" android: gravity="center" android: orientation="vertical"> <ImageView android: id="@+id/logo" android: layout_width="150dp" android: layout_height="150dp" android: layout_centerHorizontal="true" android: layout_marginTop="30dp" android: layout_marginBottom="20dp" android: src="@drawable/linshi" /> <RelativeLayout android: id="@+id/one" android: layout_width="match_parent" android: layout_height="wrap_content" android: layout_below="@id/logo" android: layout_marginTop="0dp" android: orientation="vertical"> <EditText android: id="@+id/account" android: layout_width="fill_parent" android: layout_height="43dp" android: layout_marginTop="5dp" android: hint="请输入工号" android: maxLength="20" android: paddingLeft="45dp" android: paddingRight="60dp" android: text="" android: textSize="20dp" /> <ImageView android: layout_width="28dp" android: layout_height="30dp" android: layout_gravity="left|center_horizontal" android: layout_marginStart="8dp" android: layout_marginLeft="8dp" android: layout_marginTop="@dimen/px16" android: src="@mipmap/account" android: visibility="visible" /> <RelativeLayout android: layout_width="match_parent" android: layout_height="wrap_content" android: layout_marginTop="50dp" android: orientation="horizontal"> <EditText android: id="@+id/password" android: layout_width="fill_parent" android: layout_height="43dp" android: hint="请输入密码" android: inputType="textPassword" android: maxLength="20" android: paddingLeft="45dp" android: paddingRight="60dp" android: text="" android: textSize="20dp" /> <ImageView android: layout_width="28dp" android: layout_height="30dp" android: layout_gravity="left|center_vertical" android: layout_marginStart="8dp" android: layout_marginLeft="8dp" android: layout_marginTop="@dimen/px8" android: src="@mipmap/password" /> < /RelativeLayout> < Button android: id="@+id/btn_click" android: layout_width="fill_parent" android: layout_height="40dp" android: layout_marginLeft="5dp" android: layout_marginTop="100dp" android: layout_marginRight="5dp" android: background="#ff336699" android: gravity="center" android: text="登录" android: textColor="@android: color/white" /> <CheckBox android: id="@+id/checkBoxLogin" android: layout_width="wrap_content" android: layout_height="wrap_content" android: layout_marginLeft="8dp" android: layout_marginTop="145dp" android: layout_marginRight="5dp" android: checked="true" android: text="记住用户名和密码" android: textColor="#ff0000" android: textSize="@dimen/px30" /> <TextView android: id="@+id/btn_update" android: layout_width="wrap_content" android: layout_height="wrap_content" android: layout_marginLeft="@dimen/px530" android: layout_marginTop="150dp" android: background="#ffffff" android: text="更改密码" android: textColor="#ff0000" android: textSize="@dimen/px30" /> </RelativeLayout> </RelativeLayout> </LinearLayout> 在以上代码中,在RelativeLayout布局(即相对布局)中设置android: orientation="vertical",表示此时的排列方式为垂直方向。android: gravity="center",表示该布局中子控件要居中显示。android: layout_marginLeft="20dp",表示距离左边框的距离为20dp。android: layout_marginRight="20dp",表示距离右边框的距离为20dp。android: layout_marginTop="30dp",表示距离顶部边框的距离为30dp。android: layout_marginBottom="20dp",表示距离底部边框的距离为20dp。在相对布局中,设置android: layout_centerHorizontal="true",表示将控件置于水平方向的中心位置。设置android: paddingLeft="45dp",padding为内边框,指该控件内部内容(如文本/图片距离该控件)的边距,在本例中id为account的EditText控件中,表示hint内容左内边距为45dp。 2. 编写登录接口 @POST("user/getLogin") Observable<Response<UserInfo>> login(@Query("username") String username, @Query("password") String password); 该接口传入用户工号与密码即可实现登录操作。 3. 编写登录Controller 在com.qianfeng.mis.ui.login包下新建LoginActivity类,该类用于登录、自动登录与记住密码。具体代码如下所示。 public class LoginActivity extends BaseActivity implements View.OnClickListener { private Button btnClick; private EditText userid; private EditText password; private TextView tvupdate; private String username; private String pwd; private CheckBox checkbox; @Override protected int setLayoutResId() { return R.layout.activity_login; } @Override public void initView() { btnClick = findViewById(R.id.btn_click); tvupdate = findViewById(R.id.btn_update); userid = findViewById(R.id.account); password = findViewById(R.id.password); checkbox = findViewById(R.id.checkBoxLogin); btnClick.setOnClickListener(LoginActivity.this); tvupdate.setOnClickListener(LoginActivity.this); checkbox.setOnClickListener(LoginActivity.this); String fuseraccount = SharedPreferencesHelperScan.getInstance(this). getStringValue("us"); String pwd = SharedPreferencesHelperScan.getInstance(this).getStringValue("pa"); userid.setText(fuseraccount); password.setText(pwd); } @Override public void onClick(View view) { switch (view.getId( ) ) { case R.id.btn_click: username = userid.getText().toString().trim(); pwd = password.getText().toString().trim(); if (TextUtils.isEmpty( username) || TextUtils.isEmpty( pwd) ) { ToastUitl.showShort("请输入用户名或密码"); } else { if (checkbox.isChecked( ) ) { //记住密码 SharedPreferencesHelperScan.getInstance(this). putStringValue("us", username); SharedPreferencesHelperScan.getInstance(this).putStringValue("pa", pwd); } else { SharedPreferencesHelperScan.getInstance(this).putStringValue("us", ""); SharedPreferencesHelperScan.getInstance(this).putStringValue("pa", ""); } login(username, pwd); //登录 } break; case R.id.btn_update: Intent intent = new Intent(LoginActivity.this, UpdatePasswordActivity.class); startActivity(intent); default: break; } } private void login(String username, String pwd) { RestClient.getInstance() .getStatisticsService() .login(username, pwd) .subscribeOn(Schedulers.io( ) ) .compose(bindToLifecycle( ) ) .observeOn(AndroidSchedulers.mainThread( ) ) .subscribe(response -> { if (response.getCode( ) == 200) { //登录成功,保存用户所有信息 SharedPreferencesHelperScan.getInstance(this).setUserBean(response.getResult( ) ); //保存token if (response.getHeader( ) != null) { SharedPreferencesHelperScan.getInstance(this).putStringValue("token", response.getHeader( ) ); } //将用户名保存下来 SharedPreferencesHelperScan.getInstance(this).putStringValue("username", username); SharedPreferencesHelperScan.getInstance(this).putStringValue("fname", response.getResult() .getFname( ) ); ToastUitl.showShort("登录成功"); //跳转到主界面 Intent intent = new Intent(LoginActivity.this, WorkActivity.class); intent.putExtra("fname", response.getResult( ) .getFname( ) ); intent.putExtra("fuseraccount", response.getResult() .getFuseraccount( ) ); startActivity(intent); } else { ToastUitl.showShort("登录失败 " + response.getMessage()); } }, throwable -> { ToastUitl.showShort(throwable.getMessage( ) ); }); } } 图3.4修改密码 在LoginActivity类中,首先对各个控件进行初始化与设置监听事件,通过SharedPreferencesHelperScan帮助类取出用户保存的工号与密码并设置到对应的EditText中。在onClick(View view)方法中,对用户输入的工号与密码进行判断,如果选择记住密码,则调用SharedPreferencesHelperScan帮助类的putStringValue()方法将工号与密码保存,然后调用login(String username, String pwd)方法完成登录操作,并且把UserBean与token保存下来用于后面的操作。 3.2.2修改用户密码 1. 编写修改用户密码View 编写代码,实现如图3.4所示的效果。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width="wrap_content" android: layout_height="match_parent" android: orientation="vertical" android: background="#ffffff" android: filterTouchesWhenObscured="true"> <TextView android: id="@+id/tv_scanResult" android: layout_width="match_parent" android: layout_height="40dp" android: layout_marginTop="0dp" android: gravity="center" android: text="密码修改页面" android: textSize="20dp" android: background="#336699" android: textColor="#ffffff"/> <RelativeLayout android: id="@+id/login_layout" android: layout_width="wrap_content" android: layout_height="658dp" android: layout_marginLeft="20dp" android: layout_marginRight="20dp" android: gravity="center" android: orientation="vertical"> < RelativeLayout android: id="@+id/one" android: layout_width="match_parent" android: layout_height="match_parent" android: layout_marginTop="150dp" android: orientation="vertical"> < EditText android: id="@+id/account" android: layout_width="fill_parent" android: layout_height="43dp" android: layout_marginTop="5dp" android: hint="请输入工号" android: maxLength="20" android: paddingLeft="55dp" android: paddingRight="60dp" android: text="" android: textSize="20dp" /> < ImageView android: layout_width="31dp" android: layout_height="35dp" android: layout_gravity="left|center_horizontal" android: layout_marginStart="8dp" android: layout_marginLeft="8dp" android: src="@mipmap/account" android: visibility="visible" /> <RelativeLayout android: layout_width="match_parent" android: layout_height="match_parent" android: layout_marginTop="50dp" android: orientation="horizontal"> < EditText android: id="@+id/password" android: layout_width="fill_parent" android: layout_height="43dp" android: hint="请输入新密码" android: inputType="textPassword" android: maxLength="20" android: paddingLeft="55dp" android: paddingRight="60dp" android: text="" android: textSize="20dp" /> < ImageView android: layout_width="31dp" android: layout_height="35dp" android: layout_gravity="left|center_vertical" android: layout_marginStart="8dp" android: layout_marginLeft="8dp" android: src="@mipmap/password" /> < RelativeLayout android: layout_width="match_parent" android: layout_height="match_parent" android: layout_marginTop="50dp" android: orientation="vertical"> < EditText android: id="@+id/repassword" android: layout_width="fill_parent" android: layout_height="43dp" android: hint="请再次输入新密码" android: inputType="textPassword" android: maxLength="20" android: paddingLeft="55dp" android: paddingRight="60dp" android: text="" android: textSize="20dp" /> < ImageView android: layout_width="31dp" android: layout_height="35dp" android: layout_gravity="left|center_vertical" android: layout_marginStart="8dp" android: layout_marginLeft="8dp" android: src="@mipmap/password" /> < /RelativeLayout> < /RelativeLayout> < Button android: id="@+id/btn_commit" android: layout_width="fill_parent" android: layout_height="40dp" android: layout_marginLeft="5dp" android: layout_marginRight="5dp" android: layout_marginTop="200dp" android: background="#ff336699" android: gravity="center" android: text="提交" android: textColor="@android: color/white" /> < /RelativeLayout> < /RelativeLayout> < /LinearLayout> 在该页面,所用的布局与控件较为简单,主要通过三个EditText来收集数据,通过Button提交相关数据。 2. 编写修改用户密码接口 @PUT("user/updatePassword") Observable<Response<UserInfo>> updatePassword(@Query("fuseraccount") String fuseraccount, @Query("mobilepassword") String mobilepassword); 该接口有两个参数,分别是工号fuseraccount与新密码mobilepassword,通过调用该接口实现修改用户密码。 3. 编写修改用户密码Controller 在com.qianfeng.mis.ui.login包下新建UpdatePasswordActivity,该Activity用于修改密码。关键代码如下所示。 public class UpdatePasswordActivity extends BaseActivity implements View.OnClickListener { private Button btncommit; private EditText userid; private EditText password; private EditText repassword; private String username; private String pwd; private String repwd; @Override protected int setLayoutResId() { return R.layout.activity_update_password; } @Override public void initView() { btncommit = findViewById(R.id.btn_commit); userid = findViewById(R.id.account); password = findViewById(R.id.password); repassword = findViewById(R.id.repassword); btncommit.setOnClickListener(UpdatePasswordActivity.this); } @Override public void onClick(View view) { username = userid.getText().toString().trim(); pwd = password.getText().toString().trim(); repwd = repassword.getText().toString().trim(); if(TextUtils.isEmpty( username) &&TextUtils.isEmpty( pwd) ){ ToastUitl.showShort("用户名或密码不能为空"); } else if (pwd.equals( "") &&repwd.equals( "") ) { //判断两次密码是否为空 Toast.makeText(getApplicationContext( ) , "密码不能为空", Toast.LENGTH_SHORT).show(); } else if (!pwd.equals( repwd) ){ Toast.makeText(getApplication() ,"密码不一致,请重新输入", Toast.LENGTH_SHORT).show(); } else { updatePassword(username,pwd); } } public void updatePassword(String username, String pwd){ RestClient.getInstance() .getStatisticsService() .updatePassword(username,pwd) .subscribeOn(Schedulers.io( ) ) .compose(bindToLifecycle( ) ) .observeOn(AndroidSchedulers.mainThread( ) ) .subscribe(response -> { if (response.getCode( ) == 200) { ToastUitl.showShort("密码修改成功,请重新登录"); Intent intent = new Intent(UpdatePasswordActivity.this, LoginActivity.class); startActivity(intent); finish(); } else { ToastUitl.showShort(response.getMessage( ) ); } }, throwable -> { }); } } 在UpdatePasswordActivity中,首先对各个控件进行初始化并设置监听事件,在onClick(View view)方法中,获取用户输入的内容并做判断,然后调用updatePassword(String username, String pwd)方法进行密码的修改。 3.3实现首页页面 3.3.1首页页面 1. 编写首页页面View 编写代码,实现效果如图1.5所示。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: android="http://schemas.android.com/apk/res/android" android: layout_width="match_parent" android: layout_height="match_parent" android: clipChildren="false" android: orientation="vertical"> < FrameLayout android: id="@+id/fl_mains" android: layout_width="match_parent" android: layout_height="0dp" android: layout_weight="1"> < /FrameLayout> < View android: layout_width="match_parent" android: layout_height="0.3dp" android: background="#33666666" /> < RadioGroup android: id="@+id/radioGroup" android: layout_width="match_parent" android: layout_height="56dp" android: layout_gravity="bottom|center" android: background="#eee" android: clipChildren="false" android: gravity="center" android: orientation="horizontal"> < RadioButton android: id="@+id/rb_home" android: layout_width="0dp" android: layout_height="match_parent" android: layout_weight="1" android: background="@null" android: button="@null" android: drawablePadding="6dp" android: gravity="center" android: padding="5dp" android: text="工作台" android: textColor="@color/navigator_color" /> < RadioButton android: id="@+id/rb_pond" android: layout_width="0dp" android: layout_height="match_parent" android: layout_weight="1" android: background="@null" android: button="@null" android: drawablePadding="6dp" android: gravity="center" android: padding="5dp" android: text="待办" android: textColor="@color/navigator_color" /> < LinearLayout android: gravity="center_horizontal" android: orientation="vertical" android: layout_width="0dp" android: layout_weight="1" android: layout_height="110dp"> < ImageView android: id="@+id/rbAdd" android: layout_width="55dp" android: layout_height="55dp" android: src="@mipmap/comui_tab_post" /> < TextView android: textColor="@color/black" android: text="发布" android: padding="5dp" android: layout_width="wrap_content" android: layout_height="wrap_content" /> < /LinearLayout> < RadioButton android: id="@+id/rb_message" android: layout_width="0dp" android: layout_height="match_parent" android: layout_weight="1" android: background="@null" android: button="@null" android: drawablePadding="6dp" android: gravity="center" android: padding="5dp" android: text="动态" android: textColor="@color/navigator_color" /> < RadioButton android: id="@+id/rb_me" android: layout_width="0dp" android: layout_height="match_parent" android: layout_weight="1" android: background="@null" android: button="@null" android: drawablePadding="6dp" android: gravity="center" android: padding="5dp" android: text="我的" android: textColor="@color/navigator_color" /> < /RadioGroup> < /LinearLayout> 以上代码中,使用帧布局FrameLayout来加载要被替换的Fragment,在帧布局中使用android: layout_weight="1",使其占满整个空间,通过底部的RadioButton来切换Fragment。 2. 编写首页页面Controller 在com.qianfeng.mis.ui.sale包下新建WorkActivity,该Activity为主Activity。具体代码如下所示。 public class WorkActivity extends BaseActivity { ... private WorkMainFragment mWorkMainFragment; private NewsFragment mNewsFragment1; private NewsFragment mNewsFragment2; private NewsFragment mNewsFragment3; private Fragment mContent; @Override protected int setLayoutResId() { return R.layout.activity_work; } @Override public void initView() { initListener(); mRbHome.setChecked(true); //默认选中工作台 Drawable dbHome = getResources().getDrawable(R.drawable.selector_home); dbHome.setBounds(0, 0, UIUtils.dip2Px( this, 20) , UIUtils.dip2Px( this, 20)); mRbHome.setCompoundDrawables(null, dbHome, null, null); Drawable dbPond = getResources().getDrawable(R.drawable.selector_pond); dbPond.setBounds(0, 0, UIUtils.dip2Px( this, 20) , UIUtils.dip2Px( this, 20)); mRbPond.setCompoundDrawables(null, dbPond, null, null); Drawable dbMsg = getResources().getDrawable(R.drawable.selector_message); dbMsg.setBounds(0, 0, UIUtils.dip2Px( this, 20) , UIUtils.dip2Px( this, 20) ); mRbMessage.setCompoundDrawables(null, dbMsg, null, null); Drawable dbMe = getResources().getDrawable(R.drawable.selector_person); dbMe.setBounds(0, 0, UIUtils.dip2Px( this, 20) , UIUtils.dip2Px( this, 20)); mRbMe.setCompoundDrawables(null, dbMe, null, null); initFragment(); } private void initFragment() { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (mWorkMainFragment != null && mWorkMainFragment.isAdded( ) ) { transaction.remove(mWorkMainFragment); } if (mNewsFragment1 != null && mNewsFragment1.isAdded( ) ) { transaction.remove(mNewsFragment1); } if (mNewsFragment2 != null && mNewsFragment2.isAdded( ) ) { transaction.remove(mNewsFragment2); } if (mNewsFragment3 != null && mNewsFragment3.isAdded( ) ) { transaction.remove(mNewsFragment3); } transaction.commitAllowingStateLoss(); mWorkMainFragment = null; mNewsFragment1 = null; mNewsFragment2 = null; mNewsFragment3 = null; mRbHome.performClick(); } private void initListener() { mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener( ) { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { switch (checkedId) { case R.id.rb_home: switchContent(mRbHome); break; case R.id.rb_pond: switchContent(mRbPond); break; case R.id.rb_message: switchContent(mRbMessage); break; case R.id.rb_me: switchContent(mRbMe); break; default: break; } } }); mRbAdd.setOnClickListener(new View.OnClickListener( ) { @Override public void onClick(View v) { } }); } public void switchContent(View view) { Fragment fragment; if (view == mRbHome) { if (mWorkMainFragment == null) { mWorkMainFragment = new WorkMainFragment(); } fragment = mWorkMainFragment; } else if (view == mRbPond) { if (mNewsFragment1 == null) { mNewsFragment1 = new NewsFragment(); } fragment = mNewsFragment1; } else if (view == mRbMessage) { if (mNewsFragment2 == null) { mNewsFragment2 = new NewsFragment(); } fragment = mNewsFragment2; } else if (view == mRbMe) { if (mNewsFragment3 == null) { mNewsFragment3 = new NewsFragment(); } fragment = mNewsFragment3; } else { return; } FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (mContent == null) { transaction.add(mFlMains.getId( ) , fragment).commit(); mContent = fragment; } if (mContent != fragment) { if (!fragment.isAdded( ) ) { transaction.hide(mContent).add(mFlMains.getId( ) , fragment).commitAllowingStateLoss(); } else { transaction.hide(mContent).show(fragment).commitAllowingStateLoss(); } mContent = fragment; } } } 在WorkActivity类中,在initView()方法中,首先调用 initListener()方法对RadioGroup进行事件监听,通过mRbHome.setChecked(true)方法默认选中工作台Fragment。在initFragment()方法中对Fragment进行初始化操作,在switchContent(View view)方法中显示选中的Fragment。 3.3.2工作台页面 1. 编写工作台页面View 编写代码,实现效果如图1.5所示。 <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.NestedScrollView xmlns: android="http://schemas.android.com/apk/res/android" xmlns: app="http://schemas.android.com/apk/res-auto" android: id="@+id/refreshLayout" android: layout_width="match_parent" android: layout_height="match_parent" android: background="#fff" android: orientation="vertical"> < LinearLayout android: layout_width="match_parent" android: layout_height="match_parent" android: gravity="center_horizontal" android: orientation="vertical"> < include layout="@layout/title" /> < com.qianfeng.mis.view.BannerLayout xmlns: app="http://schemas.android.com/apk/res-auto" android: id="@+id/bannerLayout" android: layout_width="match_parent" android: layout_height="@dimen/px340" app: autoPlayDuration="4000" app: indicatorMargin="5dp" app: indicatorPosition="rightBottom" app: indicatorShape="oval" app: indicatorSpace="3dp" app: isAutoPlay="true" app: scrollDuration="900" app: selectedIndicatorColor="#222222" app: selectedIndicatorHeight="4dp" app: selectedIndicatorWidth="4dp" app: unSelectedIndicatorColor="#999" app: unSelectedIndicatorHeight="4dp" app: unSelectedIndicatorWidth="4dp"> < /com.qianfeng.mis.view.BannerLayout> < RelativeLayout android: layout_width="match_parent" android: layout_height="wrap_content"> < android.support.v7.widget.RecyclerView android: id="@+id/icon_selected" android: layout_width="match_parent" android: layout_height="wrap_content" android: layout_margin="@dimen/px20" android: background="@drawable/stoke_e1e2e4" android: overScrollMode="never" /> < /RelativeLayout> < LinearLayout android: layout_width="match_parent" android: layout_height="wrap_content" android: layout_margin="@dimen/px20" android: background="@drawable/stoke_f9ebc5" android: orientation="vertical"> < TextView style="@style/textview_333_30" android: layout_marginLeft="@dimen/px30" android: layout_marginTop="@dimen/px30" android: text="产品中心" android: textColor="#F00" android: textSize="@dimen/px34" /> < android.support.v7.widget.RecyclerView android: id="@+id/rv_chanpin" android: layout_width="match_parent" android: layout_height="wrap_content" android: overScrollMode="never" /> < /LinearLayout> < /LinearLayout> < /android.support.v4.widget.NestedScrollView> 在该页面,由于有的手机屏幕较小,需要一个支持嵌套滑动NestedScrollView包裹整个布局。NestedScrollView与ScrollView比较类似,其作用就是作为控件父布局,从而具备(嵌套)滑动功能。通过include标签可以将相同的布局引入,从而避免每次编写重复代码。使用自定义的BannerLayout实现轮播图效果。各个功能导航按钮与产品中心都是使用RecyclerView实现。 2. 编写工作台页面Controller 在com.qianfeng.mis.ui.sale包下新建WorkMainFragment,该Fragment用于工作台首页的展示。具体代码如下所示。 public class WorkMainFragment extends BaseFragment { ... private int[] iconList = { R.drawable.caishen_1, R.drawable.caishen_2, R.drawable.caishen_3, R.drawable.caishen_4, R.drawable.caishen_5, R.drawable.caishen_6, R.drawable.caishen_7, R.drawable.caishen_8, R.drawable.caishen_9, R.drawable.caishen_10, R.drawable.caishen_11, R.drawable.caishen_12, }; private String[] nameList = { "客户资料", "任务/跟进", "销售机会", "报价记录", "合同/订单", "回款记录", "客户公共池", "产品信息", "数据审核", "费用报销", "库存出货", "更多"}; private int[] mIconbenner = { R.mipmap.banner_1, R.mipmap.banner_2, R.mipmap.banner_3}; int[] mChanpinlist = { R.mipmap.hm_zd240, R.mipmap.hm_zd350a, R.mipmap.hm_zd350d, R.mipmap.hm_zd350e600,}; String[] mChanpinnameList1 = { "移动产业", "千锋教育", "项目研发", "创业孵化"}; @Override protected int setLayoutResId() { return R.layout.fragment_workmain; } @Override public void initView() { mTitle.setText("工作台(" + SharedPreferencesHelperScan.getInstance(getActivity()).getStringValue("fname") + ")"); //轮播图 initBannerData(); //导航图标 setIcon (); //产品中心 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext( ) ); linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); mRvChanpin.setLayoutManager(linearLayoutManager); List< IconInfo> list1 = new ArrayList< > (); for (int i = 0; i < mChanpinlist.length; i++) { IconInfo iconInfo = new IconInfo(); iconInfo.setImage(mChanpinlist[i]); iconInfo.setName(mChanpinnameList1[i]); list1.add(iconInfo); } ChanpinMainAdapter chanpinMainAdapter = new ChanpinMainAdapter(list1); mRvChanpin.setAdapter(chanpinMainAdapter); } //初始化轮播图 private void initBannerData() { if (mIconbenner != null && mIconbenner.length > 0) { List< Integer> urls = new ArrayList< > (); for (int i = 0; i < mIconbenner.length; i++) { urls.add(mIconbenner[i]); if (mIconbenner.length > 1) { mBannerLayout.setAutoPlay(true); } else { mBannerLayout.setAutoPlay(false); } } if (urls != null && urls.size( ) > 0) { mBannerLayout.setViewRes(urls); } mBannerLayout.setOnBannerItemClickListener(new BannerLayout. OnBannerItemClickListener( ) { @Override public void onItemClick(int position) { } }); } } //设置导航图标 private void setIcon () { mIconSelected.setLayoutManager(new GridLayoutManager( getActivity( ) , 4) ); List< IconInfo> list = new ArrayList< > (); for (int i = 0; i < iconList.length; i++) { IconInfo iconInfo = new IconInfo(); iconInfo.setImage(iconList[i]); iconInfo.setName(nameList[i]); list.add(iconInfo); } IconMainAdapter icon_main_adapter = new IconMainAdapter(list); mIconSelected.setAdapter(icon_main_adapter); icon_main_adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener( ) { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { if (list.get( position) .getName( ) .equals( "客户资料")) { Intent intent = new Intent(getActivity( ) , CustomerListActivity.class); startActivity(intent); } else if (list.get(position) .getName( ) .equals( "任务/跟进")) { Intent intent = new Intent(getActivity( ), CustomGenjinActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "销售机会")) { Intent intent = new Intent(getActivity( ), CustomXiaoShouJiHuiActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "产品信息")) { Intent intent = new Intent(getActivity( ), ProductInformationListActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "报价记录")) { Intent intent = new Intent(getActivity( ), ProductOfferByIdListActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "合同/订单")) { Intent intent = new Intent(getActivity(), ContractOrderListActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "客户公共池")) { Intent intent = new Intent(getActivity( ), PublicCustomerActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "回款记录")) { Intent intent = new Intent(getActivity( ), HuiKuanJiHuaActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "数据审核")) { Intent intent = new Intent(getActivity( ), DataAuditListActivity.class); startActivity(intent); } else if (list.get( position) .getName( ) .equals( "费用报销")) { Intent intent = new Intent(getActivity( ), ReimbursementListActivity.class); startActivity(intent); } else if (list.get( position).getName( ) .equals( "库存出货")) { Intent intent = new Intent(getActivity( ), StockActivity.class); startActivity(intent); } } }); } } 在WorkMainFragment类中,首先对各个控件和相关数据进行初始化,在initView()方法中调用initBannerData()方法初始化轮播图, 调用setIcon()方法初始化导航图标,分别给各个图标设置单击事件,然后设置产品中心的adapter。