# Simple-Effect **Repository Path**: xk857/Simple-Effect ## Basic Information - **Project Name**: Simple-Effect - **Description**: 简效APP,原生Android编写 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-04-21 - **Last Updated**: 2025-03-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 通用功能 # 修改主题资源信息 ### Colors > 颜色一般都以“xui_config_color_”开头。 | 属性名 | 属性值 | 备注 | |-----------------------------------------|-----------|-----------------------------------------| | xui_config_color_main_theme | #299EE3 | 主题颜色 | | xui_config_color_light_blue | #299EE3 | 浅蓝色(重点提示文字和链接文字的颜色) | | xui_config_color_dark_blue_gray | #223B53 | 深蓝灰(标题文字的颜色) | | xui_config_color_middle_blue_gray | #6A798E | 中蓝灰(正文文字的颜色) | | xui_config_color_light_blue_gray | #9FABBC | 浅蓝灰(辅助说明文字的颜色) | | xui_config_color_transparent | #00000000 | 透明色 | | xui_config_color_white | #FFFFFF | 白色 | | xui_config_color_red | #F15C58 | 红色(错误提示颜色) | | xui_config_color_black | #333333 | 黑色(输入框和下拉框文字的颜色) | | xui_config_color_pure_yellow | #FFFF00 | 黄色 | | xui_config_color_light_yellow | #FFFFC0 | 浅黄色 | | xui_config_color_waring | #FEC005 | 警示色(提醒) | | xui_config_color_background_pressed | #F2F6F9 | 条目点击后的背景颜色 | | xui_config_color_background_tablet | #EAEDEF | 平板窗体默认背景色 | | xui_config_color_background_phone | #EEF3F7 | 手机窗体默认背景色 | | xui_config_color_separator_light_tablet | #F0F2F4 | 小的分割块使用浅色的分割线(平板) | | xui_config_color_separator_light_phone | #F0F2F4 | 小的分割块使用浅色的分割线(手机) | | xui_config_color_separator_dark_tablet | #D9DDE1 | 大的分割块使用深色的分割线[像listview、gridview之类](平板) | | xui_config_color_separator_dark_phone | #E3E6EA | 大的分割块使用深色的分割线[像listview、gridview之类](手机) | | xui_config_color_edittext_hint_text | #C5CDD9 | 输入框提示文字的颜色 | | xui_config_color_edittext_stroke | #BEC2C7 | 输入框边框颜色 | | xui_config_color_edittext_disable | #EDF2F7 | 不可输入的填充颜色 | ### Dimens > 尺寸一般都以“xui_config_size_”开头。 | 属性名 | 备注 | |---------------------------------------------|-----------------| | xui_config_content_spacing_horizontal | 水平间距 | | xui_config_content_spacing_vertical | 垂直间距 | | xui_config_simple_list_item_height | 简单列表的条目高度 | | xui_config_simple_list_icon_size | 简单列表的图标尺寸 | | xui_config_divider_height | 分割线的高度 | | xui_actionbar_height | 标题栏的高度 | | xui_actionbar_title_text_size | 标题栏文字的大小 | | xui_actionbar_sub_text_size | 副标题文字的大小 | | xui_actionbar_action_text_size | 标题栏两侧文字的大小 | | xui_actionbar_action_padding | 标题栏图标的padding | | xui_actionbar_side_text_padding | 标题栏两侧文字的padding | | xui_btn_view_radius | 按钮的圆角角度 | | xui_btn_view_width | 按钮的宽度 | | xui_btn_view_height | 按钮的高度 | | xui_btn_view_text_size | 按钮文字的大小 | | xui_btn_view_border_width | 按钮边框的宽度 | | xui_config_size_title_text | 标题文字大小 | | xui_config_size_content_text | 正文文字大小 | | xui_config_size_explain_text | 辅助说明文字大小 | | xui_config_size_edittext_input_text | 输入框文字的大小 | | xui_config_size_edittext_helper_text | 输入框辅助文字的大小 | | xui_config_size_edittext_components_spacing | 输入框组件的间距 | | xui_config_size_edittext_left_padding | 输入框左边的padding | | xui_config_size_edittext_radius | 输入框的圆角角度 | | xui_config_size_edittext_height | 输入框的高度 | | xui_config_size_spinner_text | 下拉框字体大小 | | xui_popup_width | 弹出窗的最大宽度 | | xui_dialog_radius_size | 对话框的圆角大小 | | xui_dialog_background_dim_amount | 对话框透明背景的灰度 | | xui_dialog_loading_padding_size | loading对话框的内间距 | | xui_dialog_loading_min_size | loading对话框的最小尺寸 | | xui_loading_view_size | loading控件的尺寸 | | xui_loading_view_width | loading环的宽度 | | xui_loading_margin_size | loading控件的外间距 | | mini_loading_view_size | 迷你loading控件的尺寸 | ### Drawables | 属性名 | 备注 | |-------------------------------------|-------------------| | xui_config_bg_spinner | 下拉框的背景 | | xui_config_bg_blue_btn | 主题蓝色按钮的背景 | | xui_config_bg_dialog_radius_white | 白底圆角对话框的背景 | | xui_config_bg_edittext | 输入框的背景 | | xui_config_list_divider | 条目的分割线 | | xui_config_list_item_selector | 条目的点击效果 | | xui_config_selectable_item_selector | 可点击内容的点击效果 | | xui_popup_arrow_up | PopupWindow的向上小箭头 | | xui_popup_arrow_down | PopupWindow的向下小箭头 | | xui_popup_bg | PopupWindow的背景颜色 | | xui_ic_return_back | 返回按钮 | | xui_ic_default_tip_btn | 默认提示按钮图标 | | xui_ic_default_clear_btn | 默认清空按钮图标 | | xui_ic_arrow_down_black | 默认向下黑色箭头(spinner) | ### Layouts | 属性名 | 备注 | |-----------------------------------|----------------------------------| | xui_adapter_listview_simple_item | 简单列表的布局 | | xui_dialog_loading | Loading加载弹窗的布局 | | xui_layout_loading_view | Loading加载控件的布局 | | msv_empty_view | 默认空页面布局 | | msv_error_view | 默认出错布局 | | msv_loading_view | 默认加载布局 | | msv_no_network_view | 默认无网络布局 | | xui_layout_spinner_drop_down_item | 下拉框列表布局 | | xui_layout_spinner_selected_item | 下拉框已选择条目的布局 | | xui_layout_stateful_template | StatefulLayout布局的模版布局 | | xui_layout_edit_spinner | 可编辑下拉框的布局 | | xui_adapter_simple_text | SimpleTextBanner的布局 | | xui_adapter_simple_image | SimpleImageBanner的布局 | | xui_adapter_simple_guide | SimpleGuideBanner的布局 | | xui_adapter_default_flow_tag_item | DefaultFlowTagAdapter默认流标签的适配器布局 | | marqueen_layout_complex_view | ComplexViewMF复合字幕的布局 | | marqueen_layout_notice_item | SimpleNoticeMF简单字幕的布局 | # 详细设计 ## OkHttp统一请求封装 ## 注册登录页详细设计 注册页和登录页使用一个Activity加三个Fragment实现,动画效果使用XUI框架实现。三个fragment功能分别是:账户密码登录页、客户注册页、手机验证码登录页。三个页面可以互相切换,使用Fragment的replace实现。登录注册页的Activity和Fragment界面设计如图5-1所示。 ![登录注册页Fragment设计1](https://student-xk857.oss-cn-shanghai.aliyuncs.com/typora/2023/01/登录注册页Fragment设计1.jpg) ### 动态刷新Fragment实现 登录注册页底部是一个Fragment,三个不同的Fragment需要在Activity中刷新,不能直接在Fragment中实现切换组件的效果。这里有两种实现方案,方案一是使用广播,Activity接收到来自Fragment的发送的广播,接收到广播消息后刷新页面,这个方案的优点是代码简单,缺点是需要消耗额外的系统资源。 方案二是使用接口回调的方式,将需要在Activity中实现的代码,在Fragment通过接口回调的方式实现,首先需要在Fragment中定义接口回调,这里使用其中一个Fragment作为例子,代码如下: ```java public class LoginFragment extends Fragment { private Consumer consumer; // 监听按钮单击事件 private void jumpPage() { // 接口回调,交给Activity处理 registerBtn.setOnClickListener(v -> consumer.accept(0)); forgetBtn.setOnClickListener(v -> consumer.accept(1)); } // 对外开放回调接口 public void setOnClickListener(Consumer consumer) { this.consumer = consumer; } } ``` Activity代码如下所示 ```java public class LoginActivity extends AppCompatActivity { private void showLogin() { // 登录页Fragment LoginFragment loginFragment = new LoginFragment(); // 注册页Fragment RegisterFragment registerFragment = new RegisterFragment(); // 验证码登录页Fragment LoginCodeFragment loginCodeFragment = new LoginCodeFragment(); loginFragment.setOnClickListener(position -> { if (position == 0) { // 跳转到注册页 getSupportFragmentManager().beginTransaction().replace(R.id.bottom_fragment, registerFragment).commit(); // 动画效果,底部上浮 ViewUtils.slideIn(frameLayout, 800, null, ViewUtils.Direction.BOTTOM_TO_TOP); changeTitleText(R.string.sign_up); } else if (position == 1) { // 跳转到验证码登录页 getSupportFragmentManager().beginTransaction().replace(R.id.bottom_fragment, loginCodeFragment).commit(); ViewUtils.slideIn(frameLayout, 800, null, ViewUtils.Direction.BOTTOM_TO_TOP); changeTitleText(R.string.login_in); } }); } /** 更改文本框内容添加动画 */ private void changeTitleText(int sign_up) { titleText.setText(sign_up); ViewUtils.fadeIn(titleText, 800, null); } } ``` ### 验证码倒计时实现 注册页和验证码方式登录页都有“发送验证码”按钮,初始是禁用状态,不同状态的样式使用selector实现,代码如下所示: ```xml ``` 当用户输入的手机号大于等于8位并且不在“倒计时”状态下才可点击,这个功能通过监听输入框实现,代码如下所示: ```java phoneEdit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { System.out.println("count="+count); if (s.length() < 8) { sendCodeBtn.setEnabled(false); } // 如果sendNum<=0,代表倒计时结束或未开始,也就是不再倒计时状态并且输入的数据长度大于等于8 if (s.length() >= 8 && sendNum <= 0) { sendCodeBtn.setEnabled(true); } } @Override public void afterTextChanged(Editable s) {} }); ``` 用户输入手机号点击“发送验证码”按钮后,调用获取验证码接口。如果后端接口提示发送成功,按钮进入不可用状态,并进入倒计时。倒计时使用线程睡眠加死循环实现,设置sendNum=60,每次循环sendNum--,代码如下所示。 ```java public class RegisterFragment extends Fragment { private int sendNum = 0; private void initView() { sendCodeBtn.setOnClickListener(v -> { if (StrUtil.hasBlank(phoneEdit.getText())) { if (getActivity() != null) { XToast.normal(getActivity(), "请先输入手机号").show(); } return; } Type type = new TypeToken>() {}.getType(); String url = OkHttpUtils.BASE_URL + "/api/v1/customer/send/code/" + phoneEdit.getText(); OkHttpUtils.getInstance().get(getActivity(),url , type, (Consumer) b -> { if (sendNum <= 0) { if (getActivity()!= null) { getActivity().runOnUiThread(() -> sendCodeBtn.setEnabled(false)); } sendNum = 60; new Thread(() -> { while (sendNum > 0) { if (getActivity() == null) { break; } if (getActivity() != null) { getActivity().runOnUiThread(() -> sendCodeBtn.setText(sendNum + "s")); } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } sendNum--; } getActivity().runOnUiThread(() -> { sendCodeBtn.setEnabled(true); sendCodeBtn.setText("发送验证码"); }); }).start(); } }); }); } } ``` ### 显示base64格式验证码图片 系统可能会收到来自黑客的攻击,黑客采用暴力算法可以破解用户的密码,因此使用验证码方式增强系统安全性。登录防刷使用的验证码是base64格式的,显示base64格式的图片需要先转成Bitmap格式,使用ImageView控件显示,核心代码如下: ```java private void getAuthCode() { Type type = new TypeToken>() {}.getType(); OkHttpUtils.getInstance().post(getActivity(), RequestEnum.LOGIN_CODE, type, null, (Consumer) loginCode -> { uuid = loginCode.getUuid(); // 解析出Base64编码的图片数据 String imageDataBytes = loginCode.getCodeUrl().substring(loginCode.getCodeUrl().indexOf(",") + 1); byte[] imageBytes = Base64.decode(imageDataBytes, Base64.DEFAULT); // 将字节数组转换为Bitmap对象 Bitmap bmp = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); // 显示图片 if (getActivity() != null) { getActivity().runOnUiThread(() -> { codeImgView.setImageBitmap(bmp); }); } }); } ``` ## 首页核心控件 ### 底部导航栏 底部导航栏使用JPTabBar第三方控件,通过注解的方式设置底部导航栏的图标和标题,代码如下所示: ```java public class MainActivity extends AppCompatActivity { @Titles private static final String[] mTitles = {"今日待办", "待办事项", "我的项目", "个人中心"}; @SeleIcons private static final int[] mSelectorIcons = {R.mipmap.icon1_select, /*……*/}; @NorIcons private static final int[] mNormalIcons = {R.mipmap.icon1, /*……*/}; private JPTabBar mTabBar; } ``` 当用户点击导航栏时,页面进行切换,这些页面由Fragment构成,切换页面核心代码如下所示: ```java mTabBar.setTabListener(new OnTabSelectListener() { @Override public void onTabSelect(int index) { switch (index) { case 0: getSupportFragmentManager().beginTransaction().replace(R.id.main_frame, todayFragment).commit(); break; case 1: getSupportFragmentManager().beginTransaction().replace(R.id.main_frame, toDoListFragment).commit(); break; case 2: getSupportFragmentManager().beginTransaction().replace(R.id.main_frame, projectFragment).commit(); break; case 3: getSupportFragmentManager().beginTransaction().replace(R.id.main_frame, personFragment).commit(); } } @Override public boolean onInterruptSelect(int index) { return false; } }); ``` ### 登录状态判断 用户进入首页时,会从SharedPreferences获取数据,如果获取到token则代表已登录,不做任何操作;如果没有token,代表未登录,跳转到登录注册页,为了防止用户返回到首页,使用`finish()`销毁当前页面。 ```java private void isLogin() { SharedPreferences preferences = this.getSharedPreferences("userInfo", MODE_PRIVATE); String token = preferences.getString("token", ""); if (StrUtil.hasBlank(token)) { startActivity(new Intent(this, LoginActivity.class)); finish(); } } ``` 文件上传 ```java ```