由于老板对我的要求,而自身基础不扎实(在此面壁思过),只能借(cao)鉴(xi)网上优秀且成熟的开源框架,并作修改,以方便使用。符合项目预期功能。
MutilmageSecletor:
https://github.com/lovetuzitong/MultiImageSelector/blob/master/README_zh.md
PhotoPicker:
https://github.com/donglua/PhotoPicker
以上是我学习的开源框架。
首先是对这两个框架的大致浏览,发现主要是:
面对着相似度极高的结构和模型,深刻感到这个世界的恶意,所为开源只不过是服务一群懒人(包括我这位初学者)。
两个框架的主要界面:
甲:
乙:
看上去功能也差不多。
开源真是初学者的巨大福音啊。
以MultiImageSelector(甲)为例分析一下。
首先我感觉这个DEMO做得好的原因是,没有花里胡哨的功能,而且,排版美观,也就是UI好看。
然后在框架的编写上有一点非常值得我去借鉴,就是他使用了比较清晰的结构,而公司的项目却将所有的Interface class等文件全部堆到一个包内,绝对不好管理。
分别是adapter、bean、utils、view这四个包,和一个主Activity和一个Fragment,然后最重要的事情是,它将这个包的具体实现的那个activity分开来了,这样就可以让我们很好的借(chao)鉴(xi)。
首先分析MainActivity.java
在看源代码的时候才发现,很多的功能其实谷歌已经帮我们实现了,就是说我们要知道有哪些API,这是比较重要的。
Intent intent = new Intent(MainActivity.this, MultiImageSelectorActivity.class);
// 是否显示拍摄图片
intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, showCamera);
// 最大可选择图片数量
intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, maxNum);
// 选择模式
intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, selectedMode);
// 默认选择
这几行是关于如何确定图片选取器的样式的。比如是否显示拍摄图片按钮,设置最大可选择图片数量、选择模式、默认选择样式。
判空,以防出错:
if(mSelectPath != null && mSelectPath.size()>0){
intent.putExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, mSelectPath);
}
startActivityForResult()
参考:
http://www.cnblogs.com/lijunamneg/archive/2013/02/05/2892616.html
startActivityForResult( )
可以一次性完成这项任务,当程序执行到这段代码的时候,假若从T1Activity跳转到下一个Text2Activity,而当这个Text2Activity调用了finish()方法以后,程序会自动跳转回T1Activity,并调用前一个T1Activity中的onActivityResult( )方法。
现在转向MultiImageSelectorActivity.java
mSubmitButton = (Button) findViewById(R.id.commit);
if(resultList == null || resultList.size()<=0){
mSubmitButton.setText(R.string.action_done);
mSubmitButton.setEnabled(false);
}else{
updateDoneText();
mSubmitButton.setEnabled(true);
}
这表明其实我们看到的那个Done按钮,确切来说是被代码控制的一个东西。
这时我想起返回的那个图标,估计也是一个按钮。
resultList存储着一些关于图片选取的信息。
@Override
public void onCameraShot(File imageFile) {
if(imageFile != null) {
// notify system
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
Intent data = new Intent();
resultList.add(imageFile.getAbsolutePath());
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
}
sendBroadcast是一个API,调用后向系统发送广播。
http://blog.csdn.net/luoshengyang/article/details/6744448
Uri.fromFile(imageFile)
imageFile也就这样获得了图片路径,然后被添加至resultList中。
setResult函数必须在Activity被finish之前调用。
http://www.cnblogs.com/lijunamneg/archive/2013/02/05/2892616.html
mFolderPopupWindow = new ListPopupWindow(getActivity());
mFolderPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
mFolderPopupWindow.setAdapter(mFolderAdapter);
这一段展示的是:
然后:
mGridView = (GridView) view.findViewById(R.id.grid);
mGridView.setAdapter(mImageAdapter);
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (mImageAdapter.isShowCamera()) {
// 如果显示照相机,则第一个Grid显示为照相机,处理特殊逻辑
if (i == 0) {
showCameraAction();
} else {
// 正常操作
Image image = (Image) adapterView.getAdapter().getItem(i);
selectImageFromGrid(image, mode);
}
} else {
// 正常操作
Image image = (Image) adapterView.getAdapter().getItem(i);
selectImageFromGrid(image, mode);
}
}
});
这一段是展示手机拥有的图片:
现在基本分析完毕。只剩下寻找文件的Adapter内容。
AppCompatActivity和Activity之间不能相互跳转,只好放弃图片左右轮播的效果。转而实现单个文件展示的效果。
FragmentActivity和Activity之间是可以相互跳转的。看来,我还是很好运的。
接下来继续分析这几个开源框架:
可以继续分析了解到Intent是如何传输数据的。
代码如下:
Bundle bundle = new Bundle();
bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
bundle.putBoolean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
bundle.putStringArrayList(MultiImageSelectorFragment.EXTRA_DEFAULT_SELECTED_LIST, resultList);
于是我们便得到以下的数据传递方式:
在MultiImageSelectorFragment中有一个叫bindData的函数:
void bindData(final Image data){
if(data == null) return;
// 处理单选和多选状态
if(showSelectIndicator){
indicator.setVisibility(View.VISIBLE);
if(mSelectedImages.contains(data)){
// 设置选中状态
indicator.setImageResource(R.drawable.btn_selected);
mask.setVisibility(View.VISIBLE);
}else{
// 未选择
indicator.setImageResource(R.drawable.btn_unselected);
mask.setVisibility(View.GONE);
}
}else{
indicator.setVisibility(View.GONE);
}
File imageFile = new File(data.path);
if (imageFile.exists()) {
// 显示图片
Picasso.with(mContext)
.load(imageFile)
.placeholder(R.drawable.default_error)
.tag(MultiImageSelectorFragment.TAG)
.resize(mGridWidth, mGridWidth)
.centerCrop()
.into(image);
}else{
image.setImageResource(R.drawable.default_error);
}
}
这个函数中有判断多选和单选的状态、显示图片的方法,使用的是开源的Picasso包,这样占用的内存会小一些。在此之前我写的关于图片加载的代码是一些大神自己开发的,自然有些慢,而这个包比较成熟,估计可以改写我的扫描图片的代码。
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if(id == LOADER_ALL) {
CursorLoader cursorLoader = new CursorLoader(getActivity(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
IMAGE_PROJECTION[4]+">0 AND "+IMAGE_PROJECTION[3]+"=? OR "+IMAGE_PROJECTION[3]+"=? ",
new String[]{"image/jpeg", "image/png"}, IMAGE_PROJECTION[2] + " DESC");
return cursorLoader;
}else if(id == LOADER_CATEGORY){
CursorLoader cursorLoader = new CursorLoader(getActivity(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
IMAGE_PROJECTION[4]+">0 AND "+IMAGE_PROJECTION[0]+" like '%"+args.getString("path")+"%'",
null, IMAGE_PROJECTION[2] + " DESC");
return cursorLoader;
}
return null;
}
这是关于如何扫描图片的代码,因为我在之前调用的一个图片库中,也有差不多的方法,所以我相信,就是这一段让我们获得了SD卡中的图片。然后我们需要展示,在这个框架中,作者并没有傻乎乎地去用new来构造一个函数,而是通过putIntent的方法来实现各个组件之间传递数据的方法。这样这些也在网易云课堂有所讲解。
目前的问题是我的项目无法在Activity和Fragment之间通信,其中Activity传入到Fragment是用的setArguments(Object)和getArguments(),但是对于Fragment如何传回数据给Activity一直没有好的解决方案。
于是有人提出通过设置回调函数来实现。所以我便在源码中找到了:
/**
* 回调接口
*/
public interface Callback{
void onSingleImageSelected(String path);
void onImageSelected(String path);
void onImageUnselected(String path);
void onCameraShot(File imageFile);
}
这边是Fragment和Activity的对话所用到的方法。
具体实现:
@Override
public void onSingleImageSelected(String path) {
Intent data = new Intent();
resultList.add(path);
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
@Override
public void onImageSelected(String path) {
if(!resultList.contains(path)) {
resultList.add(path);
}
// 有图片之后,改变按钮状态
if(resultList.size() > 0){
updateDoneText();
if(!mSubmitButton.isEnabled()){
mSubmitButton.setEnabled(true);
}
}
}
@Override
public void onImageUnselected(String path) {
if(resultList.contains(path)){
resultList.remove(path);
}
updateDoneText();
// 当为选择图片时候的状态
if(resultList.size() == 0){
mSubmitButton.setText(R.string.action_done);
mSubmitButton.setEnabled(false);
}
}
@Override
public void onCameraShot(File imageFile) {
if(imageFile != null) {
// notify system
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
Intent data = new Intent();
resultList.add(imageFile.getAbsolutePath());
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
}
我也可以在我的项目中实现一个回调函数。通过这个回调函数来实现Fragment对Activity的通信。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (Callback) activity;
}catch (ClassCastException e){
throw new ClassCastException("The Activity must implement MultiImageSelectorFragment.Callback interface...");
}
}
这也很重要,这是为了防止用户忘记实现回调函数而设置的异常。
为了能够刷新Fragment的数据,可以在一些方法上重新再加载一次布局,通过Bundle的传递和getSupportFragmentManager()的方法得以实现图片的返回键取消选择功能。
getSupportFragmentManager().beginTransaction()
.replace(R.id.image_grid, Fragment.instantiate(this, ImageSelectFragment.class.getName(), bundle))
.commit();