上一节对视频模块进行了综述(可参见 10. 视频模块 进行了解),接下来将从“我”模块(一)开始详细介绍:
- “我”模块(一)之创建数据库
- “我”模块(一)之“我”
- “我”模块(一)之注册
- “我”模块(一)之登录
- “我”模块(一)之个人资料
- “我”模块(一)之个人资料修改
知识点
- 掌握SQLite数据库的使用,能够使用数据库存储用户信息。
- 掌握“我”界面开发,能够展示用户基本信息以及该界面的功能。
- 掌握“登录”“注册”界面的开发,实现用户登录注册功能。
- 掌握“个人资料”以及“修改”界面的开发,实现用户信息的展示与修改功能。
“我”
任务综述:
根据“我”界面设计可知,该节目包含了用户头像、用户名、日历、星座、涂鸦、地图、收藏以及设置。当用户处于登录状态时,点击用户头像会跳转到“个人资料”界面;当用户处于未登录状态时,点击用户头像会跳转到“登录”界面。
4. “我”界面
任务分析:
“我”界面需要显示头像、用户名、日历、星座、涂鸦、地图、收藏条目以及设置条目,界面效果如图所示。
任务实施:
(1)创建“我”界面:fragment_me.xml。
(2)导入界面图片(9个)。
(3)添加circleimageview库。由于“我”界面中的头像用到了circleimageview库中的CircleImageView控件,因此需要添加circleimageview库到该项目中,在AS中选中项目/右击选择Open Module Settings/Dependencies/+/Library dependency,把de.hdodenhof:circleimageview:1.3.0库加入主项目中。
注意:
如果在Library dependency选项中找不到该库,则可以直接在该项目的build.gradle文件中添加如下代码。
compile 'de.hdodenhof:circleimageview:1.3.0'
(4)添加cardview-v7库。由于“我”界面中需要显示类似卡片的样式,因此可以同上在Library dependency选项,然后找到com.android.support:cardview-v7库并添加到项目中。
(5)放置界面控件。
一个ImageView控件用于显示红色背景图片;
一个Toolbar控件用于显示上下滑动的标题栏;
一个CircleImageView控件用于显示头像;
在该布局文件中,通过
fragment_me.xml
(6)放置layout_content_me.xml文件中的控件。
8个ImageView控件,其中4个分别用于显示日历图标、星座图标、涂鸦图标以及地图图标,2个用于显示收藏图标与设置图标,2个用于显示收藏与设置条目后面的箭头;
6个TextView控件,其中4个分别用于显示日历文本、星座文本、涂鸦文本以及地图文本;2个分别用于显示收藏与设置的文本;
4个View控件,其中3个用于分隔日历、星座、涂鸦、地图,1个用于分隔收藏和设置条目。
layout_content_me.xml
(7)修改colors.xml文件与dimens.xml文件。
#999999
#ebebeb
#eb413d
需要注意的是,在colors.xml文件中默认会有colorPrimary颜色,仅修改其值即可。在res/values文件夹的dimens.xml文件中添加如下代码:
25dp
14sp
120dp
16dp
40dp
8dp
56dp
(8)添加一个自定义AvatarBehavior。由于向上推动“我”界面时,头像会向上移动到屏幕左上角,向下拉动“我”界面时,头像会移动到它原来的位置,因此需要自定义一个AvatarBehavior类并继承CoordinatorLayout.Behavior
AvatarBehavior.java
/**
* 头像Behavior
*/
public class AvatarBehavior extends CoordinatorLayout.Behavior {
private static final float ANIM_CHANGE_POINT = 0.2f; //缩放动画变化的支点
private Context mContext;
private int mTotalScrollRange; //整个滚动的范围
private int mAppBarHeight; //AppBarLayout高度
private int mAppBarWidth; //AppBarLayout宽度
private int mOriginalSize; //控件原始大小
private int mFinalSize; //控件最终大小
private float mScaleSize; //控件最终缩放的尺寸,设置坐标值需要算上该值
private float mOriginalX; //原始x坐标
private float mFinalX; //最终x坐标
private float mOriginalY; //起始y坐标
private float mFinalY; //最终y坐标
private int mToolBarHeight; //ToolBar高度
private float mAppBarStartY; //AppBar的起始Y坐标
private float mPercent; //滚动执行百分比[0~1]
private DecelerateInterpolator mMoveYInterpolator; //Y轴移动插值器
private AccelerateInterpolator mMoveXInterpolator; //X轴移动插值器
//最终变换的视图,因为在5.0以上AppBarLayout在收缩到最终状态会覆盖变换后的视图,
//所以添加一个最终显示的图片
private CircleImageView mFinalView;
private int mFinalViewMarginBottom; //最终变换的视图离底部的大小
public AvatarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mMoveYInterpolator = new DecelerateInterpolator();
mMoveXInterpolator = new AccelerateInterpolator();
if (attrs != null) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.
AvatarImageBehavior);
mFinalSize = (int) a.getDimension(R.styleable.AvatarImageBehavior_finalSize,
0);
mFinalX = a.getDimension(R.styleable.AvatarImageBehavior_finalX, 0);
mToolBarHeight = (int) a.getDimension(R.styleable.
AvatarImageBehavior_toolBarHeight, 0);
a.recycle();
}
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View
dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView
child, View dependency) {
if (dependency instanceof AppBarLayout) {
_initVariables(child, dependency);
mPercent = (mAppBarStartY - dependency.getY()) * 1.0f / mTotalScrollRange;
float percentY = mMoveYInterpolator.getInterpolation(mPercent);
AnimHelper.setViewY(child, mOriginalY, mFinalY - mScaleSize, percentY);
if (mPercent > ANIM_CHANGE_POINT) {
float scalePercent = (mPercent - ANIM_CHANGE_POINT) / (1 -
ANIM_CHANGE_POINT);
float percentX = mMoveXInterpolator.getInterpolation(scalePercent);
AnimHelper.scaleView(child, mOriginalSize, mFinalSize, scalePercent);
AnimHelper.setViewX(child, mOriginalX, mFinalX - mScaleSize, percentX);
} else {
AnimHelper.scaleView(child, mOriginalSize, mFinalSize, 0);
AnimHelper.setViewX(child, mOriginalX, mFinalX - mScaleSize, 0);
}
if (mFinalView != null) {
if (percentY == 1.0f) {
//滚动到顶时才显示
mFinalView.setVisibility(View.VISIBLE);
} else {
mFinalView.setVisibility(View.GONE);
}
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && dependency
instanceof CollapsingToolbarLayout) {
//大于5.0才生成新的最终的头像,因为5.0以上AppBarLayout会覆盖变换后的头像
if (mFinalView == null && mFinalSize != 0 && mFinalX != 0 &&
mFinalViewMarginBottom != 0) {
mFinalView = new CircleImageView(mContext);
mFinalView.setVisibility(View.GONE);
//添加为CollapsingToolbarLayout子视图
((CollapsingToolbarLayout) dependency).addView(mFinalView);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mFinalView.
getLayoutParams();
params.width = mFinalSize; //设置大小
params.height = mFinalSize;
params.gravity = Gravity.BOTTOM; //设置位置,最后显示时相当于变换后的头像位置
params.leftMargin = (int) mFinalX;
params.bottomMargin = mFinalViewMarginBottom;
mFinalView.setLayoutParams(params);
mFinalView.setImageDrawable(child.getDrawable());
mFinalView.setBorderColor(child.getBorderColor());
int borderWidth = (int) ((mFinalSize * 1.0f / mOriginalSize) *
child.getBorderWidth());
mFinalView.setBorderWidth(borderWidth);
} else {
mFinalView.setImageDrawable(child.getDrawable());
mFinalView.setBorderColor(child.getBorderColor());
int borderWidth = (int) ((mFinalSize * 1.0f / mOriginalSize) *
child.getBorderWidth());
mFinalView.setBorderWidth(borderWidth);
}
}
return true;
}
/**
* 初始化变量
*/
private void _initVariables(CircleImageView child, View dependency) {
if (mAppBarHeight == 0) {
mAppBarHeight = dependency.getHeight();
mAppBarStartY = dependency.getY();
}
if (mTotalScrollRange == 0) {
mTotalScrollRange = ((AppBarLayout) dependency).getTotalScrollRange();
}
if (mOriginalSize == 0) {
mOriginalSize = child.getWidth();
}
if (mFinalSize == 0) {
mFinalSize = mContext.getResources().getDimensionPixelSize(R.dimen.
avatar_final_size);
}
if (mAppBarWidth == 0) {
mAppBarWidth = dependency.getWidth();
}
if (mOriginalX == 0) {
mOriginalX = child.getX();
}
if (mFinalX == 0) {
mFinalX = mContext.getResources().getDimensionPixelSize(R.dimen.
avatar_final_x);
}
if (mOriginalY == 0) {
mOriginalY = child.getY();
}
if (mFinalY == 0) {
if (mToolBarHeight == 0) {
mToolBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.
toolbar_height);
}
mFinalY = (mToolBarHeight - mFinalSize) / 2 + mAppBarStartY;
}
if (mScaleSize == 0) {
mScaleSize = (mOriginalSize - mFinalSize) * 1.0f / 2;
}
if (mFinalViewMarginBottom == 0) {
mFinalViewMarginBottom = (mToolBarHeight - mFinalSize) / 2;
}
}
}
(9)添加一个attrs.xml文件。由于需要设置自定义控件AvatarBehavior中的属性值的类型,因此需要创建res/values/attrs.xml文件,该文件中的name表示属性的名称,format表示属性的类型。
attrs.xml
(10)创建AnimHelper。由于需要设置AvatarBehavior控件的X轴与Y轴的坐标,因此在utils包创建一个AnimHelper类,在该类中创建setViewX()与setViewY()方法,分别用于设置X轴与Y轴坐标。
AnimHelper.java
public class AnimHelper {
private AnimHelper() {
throw new RuntimeException("AnimHelper cannot be initialized!");
}
public static void setViewX(View view, float originalX, float finalX, float percent)
{
float calcX = (finalX - originalX) * percent + originalX;
view.setX(calcX);
}
public static void setViewY(View view, float originalY, float finalY, float percent)
{
float calcY = (finalY - originalY) * percent + originalY;
view.setY(calcY);
}
public static void scaleView(View view, float originalSize, float finalSize, float
percent) {
float calcSize = (finalSize - originalSize) * percent + originalSize;
float caleScale = calcSize / originalSize;
view.setScaleX(caleScale);
view.setScaleY(caleScale);
}
}
5. 广播接收者
任务分析:
由于在个人头像修改后,需要在“我”界面更新头像,因此需要创建一个广播快速更新头像。
任务实施:
创建receiver包,在包中创建UpdateUserInfoReceiver并继承BroadcastReceiver类,同时重写onReceive()方法。
UpdateUserInfoReceiver.java
public class UpdateUserInfoReceiver extends BroadcastReceiver {
public interface ACTION {
String UPDATE_USERINFO = "update_userinfo";
}
//广播intent类型
public interface INTENT_TYPE {
String TYPE_NAME = "intent_name";
String UPDATE_HEAD = "update_head";//更新头像
}
private BaseOnReceiveMsgListener onReceiveMsgListener;
public UpdateUserInfoReceiver(BaseOnReceiveMsgListener onReceiveMsgListener) {
this.onReceiveMsgListener = onReceiveMsgListener;
}
@Override
public void onReceive(Context context, Intent intent) {
onReceiveMsgListener.onReceiveMsg(context, intent);
}
public interface BaseOnReceiveMsgListener {
void onReceiveMsg(Context context, Intent intent);
}
}
6. “我”界面逻辑代码
任务分析:
在“我”界面中需要判断用户是否登录。若用户未登录,则界面上显示“点击登录”,并且点击头像会跳转到“登录”界面;若用户已经登录,则界面上显示用户名,并且点击头像会跳转到“个人资料”界面。点击“收藏”条目时跳转到“收藏”界面,点击“设置”条目时跳转到“设置”界面。
任务实施:
(1)创建MeFragment类并实现OnClickListener接口。
(2)获取界面控件。创建界面控件的初始化方法initView(),用于获取“我”界面上所要用到的控件,并通过readLoginStatus()方法判断当前是否为登录状态,如果是,则需要设置对应控件的状态。同时在此方法中还需要处理控件的点击事件。
(3)接收广播。由于个人资料界面在修改头像成功后需要及时更新“我”界面的头像,因此需要在MeFragment类中创建一个receiver()方法接收传递过来的头像信息并更新界面头像。
(4)设置“我”界面的登录成功状态。在MeFragment类中创建一个setLoginParams()方法,用于设置登陆成功后“我”界面中的头像与用户名的显示状态。
(5)回传数据。在MeFragment类中重写onActivityResult()方法,在该方法中接收从“登录”界面(暂未创建)或者“设置”界面(暂未创建)回传过来的登录状态,从而设置“我”界面。
public class MeFragment extends Fragment implements View.OnClickListener {
private LinearLayout ll_calendar, ll_constellation, ll_scraw, ll_map;
private RelativeLayout rl_collection, rl_setting;
private CircleImageView iv_avatar;
private View view;
private UpdateUserInfoReceiver updateUserInfoReceiver;
private IntentFilter filter;
private boolean isLogin = false;
private CollapsingToolbarLayout collapsingToolbarLayout;
public MeFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_me, container, false);
initView(view);
return view;
}
private void initView(View view) {
ll_calendar = (LinearLayout) view.findViewById(R.id.ll_calendar);
ll_constellation = (LinearLayout) view.findViewById(R.id.ll_constellation);
ll_scraw = (LinearLayout) view.findViewById(R.id.ll_scraw);
ll_map = (LinearLayout) view.findViewById(R.id.ll_map);
rl_collection = (RelativeLayout) view.findViewById(R.id.rl_collection);
rl_setting = (RelativeLayout) view.findViewById(R.id.rl_setting);
iv_avatar = (CircleImageView) view.findViewById(R.id.iv_avatar);
collapsingToolbarLayout = (CollapsingToolbarLayout) view.findViewById(R.id.
collapsing_tool_bar);
collapsingToolbarLayout.setExpandedTitleTextAppearance(R.style.ToolbarTitle);
isLogin = UtilsHelper.readLoginStatus(getActivity());
setLoginParams(isLogin);
setListener();
receiver();
}
private void receiver() {
updateUserInfoReceiver = new UpdateUserInfoReceiver(
new UpdateUserInfoReceiver.BaseOnReceiveMsgListener() {
@Override
public void onReceiveMsg(Context context, Intent intent) {
String action = intent.getAction();
if (UpdateUserInfoReceiver.ACTION.UPDATE_USERINFO
.equals(action)) {
String type = intent.getStringExtra(UpdateUserInfoReceiver.
INTENT_TYPE.TYPE_NAME);
if (UpdateUserInfoReceiver.INTENT_TYPE.UPDATE_HEAD //更新头像
.equals(type)) {
String head = intent.getStringExtra("head");
Bitmap bt = BitmapFactory.decodeFile(head);
if (bt != null) {
Drawable drawable = new BitmapDrawable(bt);
iv_avatar.setImageDrawable(drawable);
} else {
iv_avatar.setImageResource(R.drawable.default_head);
}
}
}
}
});
filter = new IntentFilter(UpdateUserInfoReceiver.ACTION.UPDATE_USERINFO);
getActivity().registerReceiver(updateUserInfoReceiver, filter);
}
private void setListener() {
ll_calendar.setOnClickListener(this);
ll_constellation.setOnClickListener(this);
ll_scraw.setOnClickListener(this);
ll_map.setOnClickListener(this);
rl_collection.setOnClickListener(this);
rl_setting.setOnClickListener(this);
iv_avatar.setOnClickListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
if (updateUserInfoReceiver != null) {
getActivity().unregisterReceiver(updateUserInfoReceiver);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.ll_calendar:
Intent calendarIntent = new Intent(getActivity(), CalendarActivity.class);
startActivity(calendarIntent);
break;
case R.id.ll_constellation:
Intent constellIntent = new Intent(getActivity(), ConstellationActivity.class);
startActivity(constellIntent);
break;
case R.id.ll_scraw:
Intent scarwIntent = new Intent(getActivity(), ScrawActivity.class);
startActivity(scarwIntent);
break;
case R.id.ll_map:
Intent mapIntent = new Intent(getActivity(), MapActivity.class);
startActivity(mapIntent);
break;
case R.id.rl_collection:
if (isLogin) {
Intent collection = new Intent(getActivity(), CollectionActivity.class);
startActivity(collection);
} else {
Toast.makeText(getActivity(), "您还未登录,请先登录",
Toast.LENGTH_SHORT).show();
}
break;
case R.id.rl_setting:
if (isLogin) {
//跳转到设置界面
Intent settingIntent = new Intent(getActivity(), SettingActivity.class);
startActivityForResult(settingIntent, 1);
} else {
Toast.makeText(getActivity(), "您还未登录,请先登录",
Toast.LENGTH_SHORT).show();
}
break;
case R.id.iv_avatar:
if (isLogin) {
//跳转到个人资料界面
Intent userinfo = new Intent(getActivity(), UserInfoActivity.class);
startActivity(userinfo);
} else {
//跳转到登录界面
Intent login = new Intent(getActivity(), LoginActivity.class);
startActivityForResult(login, 1);
}
break;
}
}
/**
* 登录成功后设置我的界面
*/
public void setLoginParams(boolean isLogin) {
if (isLogin) {
String userName = UtilsHelper.readLoginUserName(getActivity());
collapsingToolbarLayout.setTitle(userName);
String head = DBUtils.getInstance(getActivity()).getUserHead(userName);
Bitmap bt = BitmapFactory.decodeFile(head);
if (bt != null) {
Drawable drawable = new BitmapDrawable(bt);//转换成drawable
iv_avatar.setImageDrawable(drawable);
} else {
iv_avatar.setImageResource(R.drawable.default_head);
}
} else {
iv_avatar.setImageResource(R.drawable.default_head);
collapsingToolbarLayout.setTitle("点击登录");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null) {
boolean isLogin = data.getBooleanExtra("isLogin", false);
setLoginParams(isLogin);
this.isLogin = isLogin;
}
}
}
(6)修改UtilsHelper.java文件。由于“我”界面需要根据登录状态设置相应的图标和控件的显示,因此需要在utils包中的UtilsHelper类中创建readLoginStatus()方法,从SharedPreferences中读取登录状态。
/**
* 从SharedPreferences中读取登录状态
*/
public static boolean readLoginStatus(Context context){
SharedPreferences sp=context.getSharedPreferences("loginInfo",Context.
MODE_PRIVATE);
boolean isLogin=sp.getBoolean("isLogin", false);
return isLogin;
}
由于在“我”界面中需要显示用户名,因此当用户处于登录状态时,需要在utils包中的UtilsHelper类中创建readLoginUserName()方法,从SharedPreferences中读取用户名。
/**
* 从SharedPreferences中读取登录用户名
*/
public static String readLoginUserName(Context context) {
SharedPreferences sp = context.getSharedPreferences("loginInfo",
Context.MODE_PRIVATE);
String userName = sp.getString("loginUserName", "");//读取登录时的用户名
return userName;
}
(7)修改DBUtils.java文件。由于“我”界面中需要根据用户名获取头像。
/*
* 根据登录名获取用户头像
*/
public String getUserHead(String userName) {
String sql = "SELECT head FROM " + SQLiteHelper.U_USERINFO + " WHERE userName=?";
Cursor cursor = db.rawQuery(sql, new String[]{userName});
String head = "";
while (cursor.moveToNext()) {
head = cursor.getString(cursor.getColumnIndex("head"));
}
cursor.close();
return head;
}
(8)修改styles.xml文件。“我”界面中需要设置标题字体的大小。
(9)修改底部导航栏。由于点击底部导航栏的“我”按钮时会出现“我”界面,因此需要找到MainActivity.java中的initView()方法,在其中添加:
MeFragment meFragment = new MeFragment();
在“alFragment.add(videoFragment);”语句下方添加:
alFragment.add(meFragment);
viewPager.setOffscreenPageLimit(3); //三个界面之间来回切换都不会重新加载数据。