现在一般的手机应用都会有上传头像的功能,我在实现这个功能的时候遇到很多问题,这里专门记录一下。
add 2018/5/10 21:05
先列举一下我出现过的问题:
1.运行时权限
2.调用系统相机拍照后crash,或者返回RESULT_CANCEL(0)
3.选择相片后得到的Uri为空或者为Uri后半段为资源ID(%1234567这种)
4.调用系统裁剪后crash
5.小米手机的特别情况
还有许多小问题,大多都是上面问题引起的并发症,就不一一列举了。
先上代码,慢慢讲。
1.布局
只关注头像那一栏就可以了,点击头像后会弹出选择页面。PopupWindow的实现如下:
1.1 新建layout文件pop_item
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#66000000"> <LinearLayout android:id="@+id/ll_pop" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:layout_marginRight="15dp" android:orientation="vertical" android:layout_alignParentBottom="true"> <Button android:id="@+id/icon_btn_camera" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/white_btn_top" android:textColor="@color/colorMainGreen" android:text="拍照"/> <View android:layout_width="match_parent" android:layout_height="1dp" /> <Button android:id="@+id/icon_btn_select" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/white_btn_bottom" android:textColor="@color/colorMainGreen" android:text="从相册选择"/> <Button android:id="@+id/icon_btn_cancel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginBottom="15dp" android:background="@drawable/white_btn" android:textColor="@color/colorMainGreen" android:text="取消"/> </LinearLayout> </RelativeLayout>
三个Button分别对应三个按钮,中间的View是两个按钮之间的线,colorMainGreen是
<color name="colorMainGreen">#40cab3</color>
1.2 可以看到三个按钮分别是上圆角,下圆角,全圆角,在drawable中新建3个xml,绘制Button样式
white_btn_top
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/white" /> <corners android:topLeftRadius="10dp" android:topRightRadius="10dp" android:bottomRightRadius="0dp" android:bottomLeftRadius="0dp"/> <stroke android:width="0dp" android:color="@android:color/white" /> </shape>
white_btn_bottom
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/white" /> <corners android:topLeftRadius="0dp" android:topRightRadius="0dp" android:bottomRightRadius="10dp" android:bottomLeftRadius="10dp"/> <stroke android:width="0dp" android:color="@android:color/white" /> </shape>
while_btn
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/white"/> <corners android:radius="10dp"/> <stroke android:width="0dp" android:color="@android:color/white" /> </shape>
简单解释一下shape的用法,每一组< />中制定一个属性(不知道是不是这么叫,但是是这个意思),在每条属性中可以指定更多的细节。solid指定填充,corners指定圆角,在corners中的radius指定了圆角的半径,stroke用于描边。
1.3 PhotoPopupWindow布局都写好了,现在我们要写自己的PhotoPopupWindow类加载它,同时给他添加点击事件。新建一个package,命名为popup(这样做的目的是使得代码结构清晰),在这个包下新建PhotoPopupWindow类,继承PopupWindow
public class PhotoPopupWindow extends PopupWindow { private static final String TAG = "PhotoPopupWindow"; private View mView; // PopupWindow 菜单布局 private Context mContext; // 上下文参数 private View.OnClickListener mSelectListener; // 相册选取的点击监听器 private View.OnClickListener mCaptureListener; // 拍照的点击监听器 public PhotoPopupWindow(Activity context, View.OnClickListener selectListener, View.OnClickListener captureListener) { super(context); this.mContext = context; this.mSelectListener = selectListener; this.mCaptureListener = captureListener; Init(); } /** * 设置布局以及点击事件 */ private void Init() { LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); assert inflater != null; mView = inflater.inflate(R.layout.pop_item, null); Button btn_camera = (Button) mView.findViewById(R.id.icon_btn_camera); Button btn_select = (Button) mView.findViewById(R.id.icon_btn_select); Button btn_cancel = (Button) mView.findViewById(R.id.icon_btn_cancel); btn_select.setOnClickListener(mSelectListener); btn_camera.setOnClickListener(mCaptureListener); btn_cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dismiss(); } }); // 导入布局 this.setContentView(mView); // 设置动画效果 this.setAnimationStyle(R.style.popwindow_anim_style); this.setWidth(WindowManager.LayoutParams.MATCH_PARENT); this.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); // 设置可触 this.setFocusable(true); ColorDrawable dw = new ColorDrawable(0x0000000); this.setBackgroundDrawable(dw); // 单击弹出窗以外处 关闭弹出窗 mView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int height = mView.findViewById(R.id.ll_pop).getTop(); int y = (int) event.getY(); if (event.getAction() == MotionEvent.ACTION_UP) { if (y < height) { dismiss(); } } return true; } }); } }
代码是很主流的写法,没什么特别的。TAG常量会出现在每一个JAVA类中,即使我用不到他。这样写便于区别Log发生的位置。两个OnClickListener需要在实例化的时候实现。
1.4 弹出框写好了,接下来是头像页面的布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/backgroundWhite" tools:context=".activity.UserInfoActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar_use