仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)

个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)

1、个人中心界面实现

1.1 个人中心中RecycleView内地址和设置的item实现

1.2 收货地址和设置的item类型

1.3 收货地址和设置的item数据适配器

1.4 个人中心根类

1.5 效果图

2、订单列表逻辑梳理和实现-1

2.1 订单数据转换类

2.2 订单数据适配类 

2.3 订单的根类

2.4 个人中心根类

2.5 效果图

3、个人信息更新实现-1

3.1 个人信息布局实现

3.2 个人信息界面更改的设置

4、个人信息更新实现-2

4.1 更新生日、性别

4.2 效果图

5、一键式相机,图片处理剪裁和动态权限封装

5.1 照片处理类

5.2 存储照片处理相关值

5.3 请求码存储类

5.4 照片处理类,补充拍照、从相册选择的逻辑

5.5 照相机调用类

5.6 动态权限处理类

5.7 头像的图片剪裁逻辑处理

6、收货地址管理功能实现

6.1 地址管理的入口

6.2 个人中心根类设置地址信息

6.3 效果图

逻辑:

在个人中心根类中,1)设置了每个按钮的点击事件以及要跳转的Fragment的逻辑,2)在RecyclerView中添加了两条item,设置了数据,给RecyclerView设置了item的点击的监听事件;在监听事件中根据item的id选择跳转的Fragment。

1、个人中心界面实现

1.1 个人中心中RecycleView内地址和设置的item实现

位于latte-ec模块main->personal->list包下的ListBean。

主要作用:RecycleView内的数据处理,采用构造者模式,传入和保存数据。

public class ListBean implements MultiItemEntity {

    private int mItemType = 0;//item的类型
    private String mImageUrl = null;//头像
    private String mText = null;
    private String mValue = null;
    private int mId = 0;
    private LatteDelegate mDelegate = null;//需要跳转,传入根布局
    private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener = null;//推送设置

    public ListBean(int mItemType, String mImageUrl, String mText, String mValue, int mId, LatteDelegate mDelegate, CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener) {
        this.mItemType = mItemType;
        this.mImageUrl = mImageUrl;
        this.mText = mText;
        this.mValue = mValue;
        this.mId = mId;
        this.mDelegate = mDelegate;
        this.mOnCheckedChangeListener = mOnCheckedChangeListener;
    }

    public String getImageUrl() {
        return mImageUrl;
    }

    public String getText() {
        if (mText == null) {
            return "";
        }
        return mText;
    }

    public String getValue() {
        if (mValue == null) {
            return "";
        }
        return mValue;
    }

    public int getId() {
        return mId;
    }

    public LatteDelegate getDelegate() {
        return mDelegate;
    }

    public CompoundButton.OnCheckedChangeListener getmOnCheckedChangeListener() {
        return mOnCheckedChangeListener;
    }

    @Override
    public int getItemType() {
        return mItemType;
    }

    public static final class Builder {

        private int id = 0;
        private int itemType = 0;
        private String imageUrl = null;
        private String text = null;
        private String value = null;
        private CompoundButton.OnCheckedChangeListener onCheckedChangeListener = null;
        private LatteDelegate delegate = null;

        public Builder setId(int id) {
            this.id = id;
            return this;
        }

        public Builder setItemType(int itemType) {
            this.itemType = itemType;
            return this;
        }

        public Builder setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
            return this;
        }

        public Builder setText(String text) {
            this.text = text;
            return this;
        }

        public Builder setValue(String value) {
            this.value = value;
            return this;
        }

        public Builder setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener onCheckedChangeListener) {
            this.onCheckedChangeListener = onCheckedChangeListener;
            return this;
        }

        public Builder setDelegate(LatteDelegate delegate) {
            this.delegate = delegate;
            return this;
        }

        public ListBean build() {
            return new ListBean(itemType, imageUrl, text, value, id, delegate, onCheckedChangeListener);
        }
    }
}

1.2 收货地址和设置的item类型

位于latte-ec模块main->personal->list包下的ListItemType。

主要作用:RecycleView内的数据的item类型。

public class ListItemType {
    public static final int ITEM_NORMAL = 20;//数据信息类型
}

1.3 收货地址和设置的item数据适配器

位于latte-ec模块main->personal->list包下的ListItemAdapter。

主要作用:RecycleView内的数据适配器,对ListBean中数据进行转化。

public class ListAdapter extends BaseMultiItemQuickAdapter {

    public ListAdapter(List data) {
        super(data);
        addItemType(ListItemType.ITEM_NORMAL, R.layout.arrow_item_layout);
    }

    @Override
    protected void convert(BaseViewHolder helper, ListBean item) {//转化数据,将设置的数据匹配到view中
        switch (helper.getItemViewType()){
            case 20:
                helper.setText(R.id.tv_arrow_text,item.getText());
                helper.setText(R.id.tv_arrow_value,item.getValue());
                break;
            default:
                break;
        }
    }
}

1.4 个人中心根类

位于latte-ec模块main->personal包下的PersonalDelegate。

主要作用:个人中心根类,传入设置和地址的item数据,通过adapter进行显示。

public class PersonalDelegate extends BottomItemDelegate {

    @BindView(R2.id.rv_personal_setting)
    RecyclerView mRvSettings = null;

    @Override
    public Object setLayout() {
        return R.layout.delegate_personal;
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {

        final ListBean address = new ListBean.Builder()//为recycleView添加的item
                .setItemType(ListItemType.ITEM_NORMAL)
                .setId(1)
                .setText("收货地址")
                .build();

        final ListBean system = new ListBean.Builder()
                .setItemType(ListItemType.ITEM_NORMAL)
                .setId(2)
                .setText("系统地址")
                .build();

        final List data = new ArrayList<>();
        data.add(address);
        data.add(system);

        //设置RecycleView
        final LinearLayoutManager manager = new LinearLayoutManager(getContext());
        mRvSettings.setLayoutManager(manager);
        final ListAdapter adapter = new ListAdapter(data);
        mRvSettings.setAdapter(adapter);
    }

}

1.5 效果图

仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第1张图片红框内为recycleView

2、订单列表逻辑梳理和实现-1

2.1 订单数据转换类

位于latte-ec模块main->personal->list->order包下的OrderListDataConverter。

主要作用:订单中点击事件的list数据转换,将JSON数据保存到ENTITY中。

public class OrderListDataConverter extends DataConverter {

    @Override
    public ArrayList convert() {

        final JSONArray array = JSON.parseObject(getJsonData()).getJSONArray("data");
        final int size = array.size();
        for (int i = 0; i < size; i++) {
            final JSONObject data = array.getJSONObject(i);
            final String thumb = data.getString("thumb");
            final String title = data.getString("title");
            final int id = data.getInteger("id");
            final double price = data.getDouble("price");
            final String time = data.getString("time");

            final MultipleItemEntity entity = MultipleItemEntity.builder()
                    .setItemType(OrderListItemType.ITEM_ORDER_LIST)
                    .setField(MultipleFields.ID, id)
                    .setField(MultipleFields.IMAGE_URL, thumb)
                    .setField(MultipleFields.TITLE, title)
                    .setField(OrderItemFields.PRICE, price)
                    .setField(OrderItemFields.TIME, time)
                    .build();

            ENTITIES.add(entity);
        }
        return ENTITIES;
    }
}

2.2 订单数据适配类 

位于latte-ec模块main->personal->list->order包下的OrderListAdapter。

主要作用:将从Conterver中存储的ENTITY中,通过key获取值,将值与视图View绑定。

public class OrderListAdapter extends MultipleRecyclerAdapter {

    private static final RequestOptions OPTIONS = new RequestOptions()
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .centerCrop()
            .dontAnimate();

    protected OrderListAdapter(List data) {
        super(data);
        addItemType(OrderListItemType.ITEM_ORDER_LIST, R.layout.item_order_list);
    }

    @SuppressLint("SetTextI18n")
    @Override
    protected void convert(MultipleViewHolder holder, MultipleItemEntity entity) {
        super.convert(holder, entity);
        switch (holder.getItemViewType()) {
            case OrderListItemType.ITEM_ORDER_LIST:
                final AppCompatImageView imageView = holder.getView(R.id.image_order_list);
                final AppCompatTextView title = holder.getView(R.id.tv_order_list_title);
                final AppCompatTextView price = holder.getView(R.id.tv_order_list_price);
                final AppCompatTextView time = holder.getView(R.id.tv_order_list_time);

                final String titleVal = entity.getFiled(MultipleFields.TITLE);
                final String timeVal = entity.getFiled(OrderItemFields.TIME);
                final double priceVal = entity.getFiled(OrderItemFields.PRICE);
                final String imageUrl = entity.getFiled(MultipleFields.IMAGE_URL);

                Glide.with(mContext)//加载图片
                        .load(imageUrl)
                        .apply(OPTIONS)
                        .into(imageView);

                title.setText(titleVal);//视图与JSON数据绑定
                price.setText("价格" + String.valueOf(priceVal));
                time.setText("时间" + timeVal);
                break;
            default:
                break;
        }
    }
}

2.3 订单的根类

位于latte-ec模块main->personal->list->order包下的OrderListDelegate。

主要作用:订单点击事件进入的list的根布局。

public class OrderListDelegate extends LatteDelegate {

    private String mType = null;//根据不同type设置不同的orderList

    @BindView(R2.id.rv_order_list)
    RecyclerView mRecyclerView = null;

    @Override
    public Object setLayout() {
        return R.layout.delegate_order_list;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Bundle args = getArguments();
        mType = args.getString(PersonalDelegate.ORDER_TYPE);//获取type,根据type请求api,后端根据get或post不同的参数,返回不同的JSON字段,实现不同的type返回不同的list列表-->同样的ui模板呈现不同的ui内容
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
    }

    @Override
    public void onLazyInitView(@Nullable Bundle savedInstanceState) {
        super.onLazyInitView(savedInstanceState);
        RestClient.builder()
                .loader(getContext())
                .url("order_list.php")
                .params("type", mType)//根据不同的类型,获取不同的JSON数据
                .success(new ISuccess() {
                    @Override
                    public void (String response) {
                        final LinearLayoutManager manager = new LinearLayoutManager(getContext());
                        mRecyclerView.setLayoutManager(manager);
                        final List data =
                                new OrderListDataConverter().setJsonData(response).convert();
                        final OrderListAdapter adapter = new OrderListAdapter(data);
                        mRecyclerView.setAdapter(adapter);

                    }
                })
                .build()
                .get();
    }
}

2.4 个人中心根类

位于latte-ec模块main->personal包下的PersonalDelegate。

主要作用:个人中心根类,通过按钮跳转到订单类。

public class PersonalDelegate extends BottomItemDelegate {
......
    public static final String ORDER_TYPE = "ORDER_TYPE";
    private Bundle mArgs = null;

    @Override
    public Object setLayout() {
        return R.layout.delegate_personal;
    }

    private void startOrderListByType(){//根据不同的type打开不同的orderList
        final OrderListDelegate delegate = new OrderListDelegate();
        delegate.setArguments(mArgs);//传递参数,可以根据参数可以获取delegate
        getParentDelegate().getSupportDelegate().start(delegate);//getParentDelegate():从父布局跳转
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mArgs = new Bundle();
    }

    @OnClick(R2.id.tv_all_order)//绑定了全部订单,通过全部订单按钮,跳转
    void onClickAllOrder(){
        mArgs.putString(ORDER_TYPE,"all");//传入type类型
        startOrderListByType();
    }
......
}

2.5 效果图

仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第2张图片

3、个人信息更新实现-1

3.1 个人信息布局实现

位于latte-ec模块main->personal->profile包下的UserProfileDelegate。

主要作用:个人信息根类,设置内部数据,每条信息的更改。

布局:内含一个toolbar和recycleView(有一个一个list信息组成)

public class UserProfileDelegate extends LatteDelegate {

    @BindView(R2.id.rv_user_profile)
    RecyclerView mRecyclerView = null;//个人信息内的recycleView的处理,在里面添加一条一条的list信息

    @Override
    public Object setLayout() {
        return R.layout.delegate_user_profile;
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
        final ListBean image = new ListBean.Builder()
                .setItemType(ListItemType.ITEM_AVATAR)
                .setId(1)
                .setImageUrl("http://i9.qhimg.com/t017d891ca365ef60b5.jpg")
                .build();

        final ListBean name = new ListBean.Builder()
                .setItemType(ListItemType.ITEM_NORMAL)
                .setId(2)
                .setText("姓名")
                .setDelegate(new NameDelegate())
                .setValue("未设置姓名")
                .build();

        final ListBean gender = new ListBean.Builder()
                .setItemType(ListItemType.ITEM_NORMAL)
                .setId(3)
                .setText("性别")
                .setValue("未设置性别")
                .build();

        final ListBean birth = new ListBean.Builder()
                .setItemType(ListItemType.ITEM_NORMAL)
                .setId(4)
                .setText("生日")
                .setValue("未设置生日")
                .build();

        final List data = new ArrayList<>();
        data.add(image);
        data.add(name);
        data.add(gender);
        data.add(birth);

        //设置RecyclerView
        final LinearLayoutManager manager = new LinearLayoutManager(getContext());
        mRecyclerView.setLayoutManager(manager);
        final ListAdapter adapter = new ListAdapter(data);
        mRecyclerView.setAdapter(adapter);
        mRecyclerView.addOnItemTouchListener(new UserProfileClickListener(this));
    }
}

1、个人信息每条的数据类型

位于latte-ec模块main->personal->profile包下的ListItemType。

主要作用:RecycleView内的数据的item类型。

public class ListItemType {
    public static final int ITEM_NORMAL = 20;//数据信息类型
    public static final int ITEM_AVATAR = 21;//头像信息数据类型
}

2、 个人信息内部的数据适配器

位于latte-ec模块main->personal->list包下的ListItemAdapter。

主要作用:RecycleView内的数据适配器,对ListBean中数据进行转化,新增头像修改样式,与其他普通的item修改样式不同。

public class ListAdapter extends BaseMultiItemQuickAdapter {

**  private static final RequestOptions OPTIONS = new RequestOptions()//加载图像
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .centerCrop()
            .dontAnimate();

    public ListAdapter(List data) {
        super(data);
        addItemType(ListItemType.ITEM_NORMAL, R.layout.arrow_item_layout);
**      addItemType(ListItemType.ITEM_AVATAR, R.layout.arrow_item_avatar);
    }

    @Override
    protected void convert(BaseViewHolder helper, ListBean item) {
        switch (helper.getItemViewType()) {
            case ListItemType.ITEM_NORMAL://文字部分的数据与view绑定加载
                helper.setText(R.id.tv_arrow_text, item.getText());
                helper.setText(R.id.tv_arrow_value, item.getValue());
                break;
            case ListItemType.ITEM_AVATAR://头像部分的数据与view绑定加载
**              Glide.with(mContext)
                        .load(item.getImageUrl())
                        .apply(OPTIONS)
                        .into((ImageView) helper.getView(R.id.img_arrow_avatar));
                break;
            default:
                break;
        }
    }
}

3、 个人中心根布局

位于latte-ec模块main->personal包下的PersonalDelegate。

主要作用:个人中心根类,通过点击头像跳转到个人信息Fragment。

public class PersonalDelegate extends BottomItemDelegate {
......
    @OnClick(R2.id.img_user_avatar)//点击头像,跳转delegate
    void onClickAvatar(){
        getParentDelegate().getSupportDelegate().start(new UserProfileDelegate());
    }
......
}

4、 效果图

仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第3张图片仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第4张图片

3.2 个人信息界面更改的设置

姓名:打开新的页面,输入条输入姓名(NameDelegate)

性别:双选

生日:data数据框

1、姓名item的更改

位于latte-ec模块main->personal->profile包下的UserProfileDelegate。

主要作用:个人信息根类,设置内部数据,每条信息的更改。

public class UserProfileDelegate extends LatteDelegate {
......
    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
......
        final ListBean name = new ListBean.Builder()
                .setItemType(ListItemType.ITEM_NORMAL)
                .setId(2)
                .setText("姓名")
**              .setDelegate(new NameDelegate())//要跳转的界面
                .setValue("未设置姓名")
                .build();
......
        //设置RecyclerView
        final LinearLayoutManager manager = new LinearLayoutManager(getContext());
        mRecyclerView.setLayoutManager(manager);
        final ListAdapter adapter = new ListAdapter(data);
        mRecyclerView.setAdapter(adapter);
**      mRecyclerView.addOnItemTouchListener(new UserProfileClickListener(this));//为RecyclerView添加点击事件
    }
}

2、recycleView中item的点击事件监听

位于latte-ec模块main->personal->profile包下的UserProfileClickListener。

主要作用:每个item的监听事件

public class UserProfileClickListener extends SimpleClickListener {

    private final LatteDelegate DELEGATE;

    private String[] mGenders = new String[]{"男", "女", "保密"};//性别数组

    public UserProfileClickListener(LatteDelegate delegate) {
        DELEGATE = delegate;
    }

    @Override
    public void onItemClick(BaseQuickAdapter adapter, final View view, int position) {
        final ListBean bean = (ListBean) baseQuickAdapter.getData().get(position);
        final int id = bean.getId();
        switch (id) {//根据id判断点击的是第几个:1:头像,2:姓名...
            case 1:
                //开始照相机或选择图片
                break;
            case 2:
                final LatteDelegate nameDelegate = bean.getDelegate();
                DELEGATE.getSupportDelegate().start(nameDelegate);//点击的是姓名,跳转修改name的界面3、
                break;
            case 3:
                getGenderDialog(new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        final TextView textView = view.findViewById(R.id.tv_arrow_value);//取出横向textView,为了将性别信息写入
                    }
                });
                break;
            case 4:
                break;
            default:
                break;
        }
    }

    private void getGenderDialog(DialogInterface.OnClickListener listener) {//通过dialog呈现信息
        final AlertDialog.Builder builder = new AlertDialog.Builder(DELEGATE.getContext());
        builder.setSingleChoiceItems(mGenders, 0, listener);//默认选择第一个:男
        builder.show();
    }

    @Override
    public void onItemLongClick(BaseQuickAdapter adapter, View view, int position) {

    }

    @Override
    public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

    }

    @Override
    public void onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {

    }
}

3、姓名根页面

位于latte-ec模块main->personal->profile->settings包下的NameDelegate。

主要作用:姓名根布局页面

public class NameDelegate extends LatteDelegate {
    @Override
    public Object setLayout() {
        return null;
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
    }
}

4、个人信息更新实现-2

4.1 更新生日、性别

1、创建日期修改工具类

位于latte-ui模块main->date包下的DateDialogUtil。

主要作用:修改日期工具类,个人信息中修改日期,弹出框的工具类

public class DateDialogUtil {

    public interface IDataListener {//当选择日期进行回调的接口
        void onDateChange(String date);
    }

    private IDataListener mDataListener = null;

    public void setDataListener(IDataListener listener) {
        this.mDataListener = listener;
    }

    public void showDialog(Context context) {//展示dialog
        final LinearLayout ll = new LinearLayout(context);
        final DatePicker picker = new DatePicker(context);//日期选择器
        final LinearLayout.LayoutParams lp =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);


        picker.setLayoutParams(lp);

        picker.init(1990, 1, 1, new DatePicker.OnDateChangedListener() {//初试时间
            @Override
            public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                final Calendar calendar = Calendar.getInstance();
                calendar.set(year, monthOfYear, dayOfMonth);
                final SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日", Locale.getDefault());//时间格式化,Locale.getDefault():系统默认时区
                final String data = format.format(calendar.getTime());//获取时间值
                if (mDataListener != null) {
                    mDataListener.onDateChange(data);
                }
            }
        });

        ll.addView(picker);//加入布局

        new AlertDialog.Builder(context)
                .setTitle("选择日期")
                .setView(ll)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {//确定按钮
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {//取消按钮
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                })
                .show();
    }

}

2、recycleView中生日、性别item的点击事件监听

位于latte-ec模块main->personal->profile包下的UserProfileClickListener。

主要作用:每个item的监听事件,生日选择的点击事件

public class UserProfileClickListener extends SimpleClickListener {
......
    @Override
    public void onItemClick(BaseQuickAdapter adapter, final View view, int position) {
        final ListBean bean = (ListBean) baseQuickAdapter.getData().get(position);
        final int id = bean.getId();
        switch (id) {
            case 1:
                ......
            case 3:
                getGenderDialog(new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        final TextView textView = view.findViewById(R.id.tv_arrow_value);
                        textView.setText(mGenders[which]);//设置性别
                        dialog.cancel();
                    }
                });
                break;
            case 4:
                final DateDialogUtil dateDialogUtil = new DateDialogUtil();

                dateDialogUtil.setDataListener(new DateDialogUtil.IDataListener() {
                    @Override
                    public void onDateChange(String date) {
                        final TextView textView = view.findViewById(R.id.tv_arrow_value);
                        textView.setText(date);//时间改变,设置新的时间
                    }
                });
                dateDialogUtil.showDialog(DELEGATE.getContext());
                break;
            default:
                break;
        }
    }

    private void getGenderDialog(DialogInterface.OnClickListener listener) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(DELEGATE.getContext());
        builder.setSingleChoiceItems(mGenders, 0, listener);
        builder.show();
    }
......
}

4.2 效果图

       点击头像       ->   修改个人信息    ->      修改姓名       ->      修改性别        ->       修改生日

仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第5张图片仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第6张图片仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第7张图片仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第8张图片仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第9张图片

5、一键式相机,图片处理剪裁和动态权限封装

相关的类:

Android模式->latte-ec->java->com.example.latte.ui->main包->新建camera包(处理照片)->新建CameraHandler.java类(照片处理类)

Android模式->latte-ec->java->com.example.latte.ui->main包->新建camera包(处理照片)->新建LatteCamera.java类(照相机调用类)

Android模式->latte-ec->java->com.example.latte.ui->main包->新建camera包(处理照片)->新建RequestCodes.java类(请求码存储)

Android模式->latte-ec->java->com.example.latte.ui->main包->新建camera包(处理照片)->新建CameraImageBean.java类(存储一些中间值)

5.1 照片处理类

位于latte-ui模块main->camera包下的CameraHandler。

主要作用:照片处理类,拍照页面弹出的效果以及拍照的逻辑处理。

public class CameraHandler implements View.OnClickListener{

    private final AlertDialog DIALOG;
    private final PermissionCheckerDelegate DELEGATE;//PermissionCheckerDelegate自己定义的,继承LatteDelegate

    public CameraHandler(AlertDialog dialog, PermissionCheckerDelegate delegate) {
        this.DIALOG = dialog;
        this.DELEGATE = delegate;
    }

    final void beginCameraDialog(){//打开一个从下向上的pancel,选择事件:照相机,从文件系统获取照片,取消
        DIALOG.show();
        final Window window = DIALOG.getWindow();
        if (window!=null){
            window.setContentView(R.layout.dialog_camera_panel);
            window.setGravity(Gravity.BOTTOM);
            window.setWindowAnimations(R.style.anim_panel_up_from_bottom);
            window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            //设置属性
            final WindowManager.LayoutParams params = window.getAttributes();
            params.width = WindowManager.LayoutParams.MATCH_PARENT;
            params.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            params.dimAmount = 0.5f;
            window.setAttributes(params);

            window.findViewById(R.id.photodialog_btn_cancel).setOnClickListener(this);
            window.findViewById(R.id.photodialog_btn_take).setOnClickListener(this);
            window.findViewById(R.id.photodialog_btn_native).setOnClickListener(this);
        }
    }

    private String getPhotoName(){//获取照片名字
        return FileUtil.getFileNameByTime("IMG","jpg");//传入前缀、后缀
    }

    private void takePhoto(){//拍照的逻辑
        final String currentPhotoName = getPhotoName();
        final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//拍照的意图
        final File tempFile = new File(FileUtil.CAMERA_PHOTO_DIR,currentPhotoName);//FileUtil.CAMERA_PHOTO_DIR:系统目录,在刷相册,拍照就显示出来了

        //兼容7.0及以上写法,应用外文件访问
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){//7.0以上系统
            final ContentValues contentValues = new ContentValues(1);//一次传入一个数据
            contentValues.put(MediaStore.Images.Media.DATA,tempFile.getPath());
            final Uri uri = DELEGATE.getContext().getContentResolver().
                    insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);//插入数据
            //需要将uri转化为实际路径

        }
    }

    @Override
    public void onClick(View v) {//不同按钮的点击事件
        int id = v.getId();
        if (id == R.id.photodialog_btn_cancel){
            DIALOG.cancel();
        }else if(id == R.id.photodialog_btn_take){
            DIALOG.cancel();
        }else if(id == R.id.photodialog_btn_native){
            DIALOG.cancel();
        }
    }
}

5.2 存储照片处理相关值

位于latte-ui模块main->camera包下的CameraImageBean。

主要作用:存储一些中间值。

public final class CameraImageBean {

    private Uri mPath = null;

    private static final CameraImageBean INSTANCE = new CameraImageBean();

    public static CameraImageBean getInstance(){//饿汉的单例模式
        return INSTANCE;
    }

    public Uri getPath() {
        return mPath;
    }

    public void setPath(Uri mPath) {
        this.mPath = mPath;
    }
}

5.3 请求码存储类

位于latte-ui模块main->camera包下的RequestCodes。

主要作用:存储照片拍照或从相册选择的请求码。

public class RequestCodes {

    public static final int TAKE_PHOTO = 4;
    public static final int PICK_PHOTO = 5;
    public static final int CROP_PHOTO = UCrop.REQUEST_CROP;
    public static final int TAKE_ERROR = UCrop.RESULT_ERROR;
    public static final int SCAN = 7;

}

5.4 照片处理类,补充拍照、从相册选择的逻辑

位于latte-ui模块main->camera包下的CameraHandler。

主要作用:照片处理类,拍照页面弹出的效果以及拍照的逻辑处理。

public class CameraHandler implements View.OnClickListener {
......
    private void takePhoto() {//拍照
        ......
        //兼容7.0及以上写法
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            ......
            //需要将uri转化为实际路径
            final File realFile =
                    FileUtils.getFileByPath(FileUtil.getRealFilePath(DELEGATE.getContext(),uri));
            final Uri realUri = Uri.fromFile(realFile);
            CameraImageBean.getInstance().setPath(realUri);
            intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);//加入参数
        }else {
            final Uri fileUri = Uri.fromFile(tempFile);
            CameraImageBean.getInstance().setPath(fileUri);
            intent.putExtra(MediaStore.EXTRA_OUTPUT,fileUri);
        }
        DELEGATE.startActivityForResult(intent,RequestCodes.TAKE_PHOTO);//调用Activity
    }

    private void pickPhoto(){//选择图片
        final Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        DELEGATE.startActivityForResult(Intent.createChooser(intent,"选择获取图片的方式"),RequestCodes.PICK_PHOTO);
//选择器,上拉选择框,上拉选择框的标题"选择获取图片的方式",调用Activity
    }
......加到点击事件中
}

5.5 照相机调用类

位于latte-ui模块main->camera包下的LatteCamera。

主要作用:照相机调用类。

public class LatteCamera {

    public static Uri createCropFile(){//剪裁文件的地址
        return Uri.parse
                (FileUtil.createFile("crop_image",
                        FileUtil.getFileNameByTime("IMG","jpg")).getPath());
    }

    public static void start(PermissionCheckerDelegate delegate){
        new CameraHandler(delegate).beginCameraDialog();//进入到具体的拍照/选择照片中
    }
}

5.6 动态权限处理类

位于latte-core模块main->delegates包下的PermissionCheckerDelegate。

主要作用:拍照的动态权限处理。

@RuntimePermissions
public abstract class PermissionCheckerDelegate extends BaseDelegate {

    //不是直接调用方法,为了生成代码使用的
    @NeedsPermission(Manifest.permission.CAMERA)
    void startCamera(){
        LatteCamera.start(this);
    }

    //这个是真正调用的方法
    public void startCameraWithCheck(){
        PermissionCheckerDelegatePermissionsDispatcher.startCameraWithPermissionCheck(this);//调用Camera,通过权限
    }

    @OnPermissionDenied(Manifest.permission.CAMERA)//动态权限
    void onCameraDenied(){
        Toast.makeText(getContext(),"不允许拍照",Toast.LENGTH_LONG).show();
    }

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    void onCameraNever() {
        Toast.makeText(getContext(), "永久拒绝权限", Toast.LENGTH_LONG).show();
    }

    @OnShowRationale(Manifest.permission.CAMERA)
    void onCameraRationale(PermissionRequest request){
        showRationaleDialog(request);//权限管理,弹出dialog,让用户选择
    }

    private void showRationaleDialog(final PermissionRequest request) {
        new AlertDialog.Builder(getContext())
                .setPositiveButton("同意使用", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();
                    }
                })
                .setNegativeButton("拒绝使用", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("权限管理")
                .show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionCheckerDelegatePermissionsDispatcher.onRequestPermissionsResult(this,requestCode,grantResults);
    }
}

5.7 头像的图片剪裁逻辑处理

1、剪裁标记值

位于latte-core模块util->callback包下的CallbackType。

主要作用:剪裁标记值。

public enum CallbackType {
    ON_CROP//剪裁后参数
}

2、裁剪的回调方法

位于latte-core模块util->callback包下的IGlobalCallback。

主要作用:剪裁标记值。

public interface IGlobalCallback {//T:剪裁后需要传的值,比如uri

    void executeCallback(T args);
}

3、裁剪的回调处理类

位于latte-core模块util->callback包下的CallbackManager。

主要作用:裁剪的回调处理类。

public class CallbackManager {

    private static final WeakHashMap CALLBACKS = new WeakHashMap<>();//存储Callback

    private static class Holder{
        private static final CallbackManager INSTANCE = new CallbackManager();
    }

    public static CallbackManager getInstance(){//惰性单例
        return Holder.INSTANCE;
    }

    public CallbackManager addCallback(Object tag,IGlobalCallback callback){
        CALLBACKS.put(tag, callback);
        return this;
    }

    public IGlobalCallback getCallback(Object tag){
        return CALLBACKS.get(tag);
    }
}

4、动态权限处理-裁剪部分的处理

位于latte-core模块main->delegates包下的PermissionCheckerDelegate。

主要作用:拍照的动态权限处理,处理裁剪的逻辑。

@RuntimePermissions
public abstract class PermissionCheckerDelegate extends BaseDelegate {
……    
@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {//处理得到照片后,将照片剪裁成图片的方法。
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {//方法正确进入选项,选择图片处理方式
            switch (requestCode) {
                case RequestCodes.TAKE_PHOTO:
                    final Uri resultUri = CameraImageBean.getInstance().getPath();//图片的路径
                    UCrop.of(resultUri, resultUri)//进行图片剪裁,剪裁后的图片覆盖原图片
                            .withMaxResultSize(400, 400)
                            .start(getContext(), this);
                    break;
                case RequestCodes.PICK_PHOTO:
                    if (data!=null){
                        final Uri pickPath = data.getData();
                        //从相册选择后需要有个路径存放剪裁过的图片
                        final String pickCropPath = LatteCamera.createCropFile().getPath();
                        UCrop.of(pickPath, Uri.parse(pickCropPath))
                                .withMaxResultSize(400, 400)
                                .start(getContext(), this);
                    }
                    break;
                case RequestCodes.CROP_PHOTO:
                    final Uri cropUri = UCrop.getOutput(data);
                    //拿到剪裁后的数据进行处理
	@SuppressWarnings("unchecked")
final IGlobalCallback callback = CallbackManager//2、3、4获取回调
        .getInstance()
        .getCallback(CallbackType.ON_CROP);//剪裁时的类型
if (callback!=null){
    callback.executeCallback(cropUri);//可以在任何时候处理回调
}
  break;
		case RequestCodes.TAKE_ERROR:
		Toast.makeText(getContext(),"剪裁出错",Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    }
}

5、个人中心设置入口

位于latte-ec模块main->personal->profile包下的UserProfileClickListener。

主要作用:每个item的监听事件,照片选择的点击事件

public class UserProfileClickListener extends SimpleClickListener {
……
        @Override
public void onItemClick(BaseQuickAdapter adapter, final View view, int position) {
    final ListBean bean = (ListBean) baseQuickAdapter.getData().get(position);
    final int id = bean.getId();
    switch (id) {
        case 1:
            //开始照相机或选择图片
            CallbackManager.getInstance()
                    .addCallback(CallbackType.ON_CROP, new IGlobalCallback() {
                        @Override
                        public void executeCallback(Uri args) {
                            LatteLogger.d("ON_CROP", args);
                            final ImageView avatar = view.findViewById(R.id.img_arrow_avatar);//获取放图片的地方
                            Glide.with(DELEGATE.getContext())//加载图片
                                    .load(args)
                                    .into(avatar);
                            RestClient.builder()//上传头像
                                    .url(UploadConfig.UPLOAD_IMG)
                                    .loader(DELEGATE.getContext())
                                    .file(args.getPath())//穿的图片地址
                                    .success(new ISuccess() {
                                        @Override
                                        public void onSuccess(String response) {
                                            LatteLogger.d("ON_CROP_UPLOAD", response);
                                            String path = JSON.parseObject(response).getJSONObject("result").getString("path");//获取图像位置信息
                                            //通知服务器更新信息,得到用户信息,在GreenDao数据库中avatar进行更改,本地的也会更新
                                            RestClient.builder()
                                                    .url("user_profile.php")
                                                    .params("avatar",path)//头像更新
                                                    .loader(DELEGATE.getContext())
                                                    .success(new ISuccess() {
                                                        @Override
                                                        public void onSuccess(String response) {
                                                            //获取更新后的用户信息,然后更新本地数据库
                                                            //没有本地数据库的App,每次打开App都请求API,获取信息

                                                        }
                                                    })
                                                    .build()
                                                    .post();
                                        }
                                    })
                                    .build()
                                    .upload();
                        }
                    });
            DELEGATE.startCameraWithCheck();
            break;
        case 2:

……        
}

6、收货地址管理功能实现

逻辑:从个人中心根布局设置入口,为每个item设置点击事件,通过id判别是哪个点击事件,进入该事件的Fragment的根页面进行逻辑处理(内含数据适配器,数据转换等)

仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第10张图片

AddressDataConverter.java:将JSON数据转换,存储成key-value

AddressAdapter.java:将转换的JSON数据与试图绑定

AddressItemType.java:存储type类型的key

AddressItemFields.java:存储JSON数据类型的key

AddressDelegate.java:地址的跟布局

6.1 地址管理的入口

位于latte-ec模块main->personal->profile包下的UserProfileClickListener。

主要作用:每个item的监听事件,根据id跳转地址界面

public class PersonalClickListener extends SimpleClickListener {

    private final LatteDelegate DELEGATE;

    public PersonalClickListener(LatteDelegate delegate) {
        this.DELEGATE = delegate;
    }

    @Override
    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
        ListBean bean = (ListBean) baseQuickAdapter.getData().get(position);
        int id = bean.getId();
        switch (id){
            case 1://根据不同id对点击事件进行处理
                DELEGATE.getParentDelegate().getSupportDelegate().start(bean.getDelegate());//在外部处理
                break;
            case 2:
                DELEGATE.getParentDelegate().getSupportDelegate().start(bean.getDelegate());
                break;
            default:
                break;
        }
    }
……
}

6.2 个人中心根类设置地址信息

位于latte-ec模块main->personal包下的PersonalDelegate。

主要作用:个人中心根类,传入地址的item数据,地址具体页面的delegate。

@Override
public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
    final ListBean address = new ListBean.Builder()
            .setItemType(ListItemType.ITEM_NORMAL)
            .setId(1)
**          .setDelegate(new AddressDelegate())
            .setText("收货地址")
            .build();

    //设置RecycleView
    final LinearLayoutManager manager = new LinearLayoutManager(getContext());
    mRvSettings.setLayoutManager(manager);
    final ListAdapter adapter = new ListAdapter(data);
    mRvSettings.setAdapter(adapter);
**  mRvSettings.addOnItemTouchListener(new PersonalClickListener(this));//给每条数据新增点击事件
}

6.3 效果图

点击收货地址进入页面,点击删除会删除地址信息

仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第11张图片仿电商App:笔记(十二):个人中心、图片裁剪、图片上传、收货地址、消息推送、权限管理等功能开发与一键式封装(一)_第12张图片

你可能感兴趣的:(latte项目)