实例模仿新版QQ相册功能,RecyclerView实现相册选择,DiskLruCache实现图片缓存,ItemTouchHelper实现图片的拖拽排序,单线程轮播解决加载大量图片卡顿问题(参考:http://blog.csdn.net/lishengko/article/details/56498844)。

BaseActivity 相册功能比较常用,单独封装了下

public abstract class BaseActivity extends AppCompatActivity {protected RecyclerView mRecyclerView;protected CameraAdapter mAdapter;protected ItemTouchHelper mItemTouchHelper;protected List<PhotoItem> photoItemList;protected ImageButton mSubmit;protected CameraDialogFragment dialogFragment;protected int photoLayout[];protected void init(int layoutId){setContentView(layoutId);mSubmit = (ImageButton)findViewById(R.id.submit);mRecyclerView = (RecyclerView) findViewById(R.id.list);mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));mRecyclerView.setHasFixedSize(true);photoItemList = new ArrayList<>();photoItemList.add(new PhotoItem(-1,"", ""));photoLayout = new int[2];photoLayout[0]=(ScreenHelper.getScreenWidth(this) - ScreenHelper.dp2px(this, 20)) / 3 - ScreenHelper.dp2px(this, 10);photoLayout[1] = ScreenHelper.dp2px(this, 110);mAdapter = new CameraAdapter(mRecyclerView,photoItemList,photoLayout);mRecyclerView.setAdapter(mAdapter);ItemTouchHelper.Callback callback = new DragItemTouchHelperCallback(mAdapter);mItemTouchHelper = new ItemTouchHelper(callback);mItemTouchHelper.attachToRecyclerView(mRecyclerView);mAdapter.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {//添加图片if(viewHolder.getItemViewType()==0){showCameraDialog();}//图片明细else {}}});}public abstract void onSubmit(View view);public void onBack(View view){finish();}protected void showCameraDialog(){if(dialogFragment==null){dialogFragment = new CameraDialogFragment();dialogFragment.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {//拍照if(position==0)BaseActivity.this.startActivityForResult(new Intent(BaseActivity.this, CameraActivity.class), 1);//相册else{Intent intent = new Intent(BaseActivity.this, AlbumActivity.class);List<PhotoItem> _photoItemList = new ArrayList<>();if(photoItemList.size()>1)_photoItemList.addAll(photoItemList.subList(0,photoItemList.size()-1));intent.putExtra(FinalHelper.IMAGE_PATH,(Serializable)_photoItemList);BaseActivity.this.startActivityForResult(intent, 2);}}});dialogFragment.show(getFragmentManager(),"dialog");}elsedialogFragment.show(getFragmentManager(),"dialog");}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if(resultCode== Activity.RESULT_OK){int start=photoItemList.size()-1;//拍照if(requestCode== FinalHelper.RequestCode_Camera){String path = data.getStringExtra(FinalHelper.IMAGE_PATH);photoItemList.add(start,new PhotoItem(0,"",path));//添加缓存CacheManager.put(path, ImageHelper.getSmallCropBitmap(path, photoLayout[0], photoLayout[1]));mAdapter.notifyItemInserted(start);ImageHelper.deleteTempleFile();}//相册else {List<PhotoItem> _photoItemList = (List<PhotoItem>)data.getSerializableExtra(FinalHelper.IMAGE_PATH);if(_photoItemList!=null && _photoItemList.size()!=0){for(int i=0;i<photoItemList.size();i++){if(photoItemList.get(i).getPhotoID()!=-1){photoItemList.remove(i--);}}photoItemList.addAll(0,_photoItemList);mAdapter.notifyDataSetChanged();}}}}
}

RepairActivity 实例Activity

public class RepairActivity extends BaseActivity {private EditText mEditText;@Overridepublic void onSubmit(View view) {}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);init(R.layout.activity_repair);mEditText = (EditText)findViewById(R.id.content);mEditText.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {}@Overridepublic void afterTextChanged(Editable s) {if (s.toString().length() != 0) {mSubmit.setClickable(true);mSubmit.setAlpha(1f);} else {mSubmit.setClickable(false);mSubmit.setAlpha(0.7f);}}});}
}

AlbumActivity 相册功能

public class AlbumActivity extends AppCompatActivity{private RecyclerView mRecyclerView;private List<PhotoItem> photoItemList;private List<PhotoItem> selectItemList;private int[] photoLayout;private AlbumAdapter mAdapter;private Handler handler;private Button submit;private GridLayoutManager gridLayoutManager;private static final String[] STORE_IMAGES = {MediaStore.Images.Media.DISPLAY_NAME, // 显示的名字MediaStore.Images.Media.LATITUDE, // 维度MediaStore.Images.Media.LONGITUDE, // 经度MediaStore.Images.Media._ID, // idMediaStore.Images.Media.BUCKET_ID, // dir id 目录MediaStore.Images.Media.BUCKET_DISPLAY_NAME, // dir name 目录名字MediaStore.Images.Media.DATA//路径};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_album);submit = (Button)findViewById(R.id.submit);mRecyclerView = (RecyclerView)findViewById(R.id.list);gridLayoutManager = new GridLayoutManager(this, 3);mRecyclerView.setLayoutManager(gridLayoutManager);mRecyclerView.setHasFixedSize(true);photoLayout = new int[2];photoLayout[0]=(ScreenHelper.getScreenWidth(this) - ScreenHelper.dp2px(this, 20)) / 3 - ScreenHelper.dp2px(this, 10);photoLayout[1] = ScreenHelper.dp2px(this, 110);gridLayoutManager.findFirstCompletelyVisibleItemPosition();initPhoto();notifySubmit();mAdapter = new AlbumAdapter(mRecyclerView,photoItemList,selectItemList);mAdapter.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {notifySubmit();}});mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {//停止滑动后缓存当前屏幕图片if(newState == 0){new Thread(new Runnable() {@Overridepublic void run() {for(int i=gridLayoutManager.findFirstVisibleItemPosition();i<gridLayoutManager.findLastVisibleItemPosition();i++){cacheBitmap(i);}}}).start();}}});mRecyclerView.setAdapter(mAdapter);handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {mAdapter.notifyImage((int)msg.obj);return false;}});}private void initPhoto(){photoItemList = new ArrayList<>();selectItemList = (List<PhotoItem>)getIntent().getSerializableExtra(FinalHelper.IMAGE_PATH);if(selectItemList == null)selectItemList = new ArrayList<>();String path="";for(int i=0;i<selectItemList.size();i++){path += selectItemList.get(i).getPath()+",";}Cursor cursor = MediaStore.Images.Media.query(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, STORE_IMAGES,"width >0","date_modified desc");cursor.moveToNext();for (int i = 0; i < cursor.getCount(); i++) {PhotoItem photoItem = new PhotoItem(cursor.getInt(3),path.indexOf(cursor.getString(6))!=-1?true:false,cursor.getString(0),cursor.getString(6));photoItemList.add(photoItem);cursor.moveToNext();}//从前往后存缓存缩略图  单线程轮播减少内存消耗确保屏幕不卡顿new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<photoItemList.size();i++){cacheBitmap(i);}}}).start();}//缓存bitmappublic void cacheBitmap(int position){if(CacheManager.get(photoItemList.get(position).getPath())==null){Bitmap bitmap = ImageHelper.getSmallCropBitmap(photoItemList.get(position).getPath(), photoLayout[0], photoLayout[1]);CacheManager.put(photoItemList.get(position).getPath(),bitmap);Message message = new Message();message.obj=position;handler.sendMessage(message);}}public void notifySubmit(){if(selectItemList.size()>0){submit.setBackgroundResource(R.drawable.bg_album_submit_enable);submit.setText(getString(R.string.submit) + "(" + selectItemList.size() + ")");submit.setTextColor(ContextCompat.getColor(this, R.color.white));submit.setAlpha(1f);submit.setClickable(true);}else {submit.setBackgroundResource(R.drawable.bg_album_submit);submit.setTextColor(ContextCompat.getColor(this, R.color.darkgray));submit.setAlpha(.8f);submit.setText(getString(R.string.submit));submit.setClickable(false);}}public void onSubmit(View view){Intent intent = new Intent();intent.putExtra(FinalHelper.IMAGE_PATH, (Serializable) selectItemList);setResult(Activity.RESULT_OK, intent);finish();}public void onBack(View view){finish();}public void onCancel(View view){finish();}}

CameraActivity 相机拍照

public class CameraActivity extends Activity {private String mImageFilePath;public String IMAGEFILEPATH = "ImageFilePath";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//判断 activity被销毁后 有没有数据被保存下来if (savedInstanceState != null) {mImageFilePath = savedInstanceState.getString(IMAGEFILEPATH);File mFile = new File(IMAGEFILEPATH);if (mFile.exists()) {Intent intent = new Intent();intent.putExtra(FinalHelper.IMAGE_PATH, mImageFilePath);setResult(Activity.RESULT_OK, intent);} else {Toast.makeText(this, "图片拍摄失败", Toast.LENGTH_LONG).show();}insertToCamera();finish();}elseinitialUI();}public void initialUI() {File cameraFile = null;Date date = new Date();try {String dir = FileHelper.getImageFilePath();if(!dir.isEmpty()){cameraFile = new File(dir+File.separator+date.getTime()+".jpg");cameraFile.createNewFile();}} catch (IOException e) {e.printStackTrace();}if(cameraFile !=null){mImageFilePath = cameraFile.getPath();Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cameraFile)); // setstartActivityForResult(intent, FinalHelper.RequestCode_Camera);}else{Toast.makeText(this, "未发现SD卡或SD卡不可用", Toast.LENGTH_LONG).show();finish();}}//插入到系统相册private void insertToCamera(){new Thread(new Runnable() {@Overridepublic void run() {File file = new File(mImageFilePath);try {MediaStore.Images.Media.insertImage(getContentResolver(), file.getAbsolutePath(), file.getName(), null);sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));}catch (Exception e){e.printStackTrace();}}}).start();}@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent intent) {if (FinalHelper.RequestCode_Camera == requestCode && resultCode == Activity.RESULT_OK) {Intent rsl = new Intent();rsl.putExtra(FinalHelper.IMAGE_PATH, mImageFilePath);setResult(Activity.RESULT_OK, rsl);insertToCamera();finish();} else {finish();}}
}

AlbumAdapter(AlbumActivity) 相册适配器

public class AlbumAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{private List<PhotoItem> photoItemList;private List<PhotoItem> selectItemList;private RecyclerView mRecyclerView;private OnItemClickListener onItemClickListener;public AlbumAdapter(RecyclerView recyclerView,List<PhotoItem> photoItemList,List<PhotoItem> selectItemList){this.photoItemList = photoItemList;this.selectItemList = selectItemList;this.mRecyclerView = recyclerView;}public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}//更新图片public void notifyImage(int position){if(mRecyclerView.findViewWithTag(position)!=null){ImageView imageView = (ImageView)mRecyclerView.findViewWithTag(position);imageView.setImageBitmap(CacheManager.get(photoItemList.get(position).getPath()));imageView.setVisibility(View.VISIBLE);}}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.album_list_photo, parent, false);AlbumViewHolder viewHolder = new AlbumViewHolder(view);return viewHolder;}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {final AlbumViewHolder viewHolder = (AlbumViewHolder)holder;viewHolder.photo.setVisibility(View.GONE);viewHolder.photo.setTag(position);viewHolder.select.setTag(photoItemList.get(position).getPath());setSelectBackground(viewHolder, photoItemList.get(position));Bitmap bitmap = CacheManager.get(photoItemList.get(position).getPath());if(bitmap!=null){viewHolder.photo.setImageBitmap(bitmap);viewHolder.photo.setVisibility(View.VISIBLE);}viewHolder.photo.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {photoItemList.get(position).setSelect(!photoItemList.get(position).isSelect());if (photoItemList.get(position).isSelect()) {selectItemList.add(photoItemList.get(position));} else {//selectItemList.remove(ListHelper.getIndex(selectItemList,photoItemList.))removeSelectItem(photoItemList.get(position));}setSelectBackground(viewHolder, photoItemList.get(position));setAllSelectBackground();onItemClickListener.onItemClick(viewHolder, position);}});}//更新当前照片索引private void setSelectBackground(AlbumViewHolder viewHolder,PhotoItem photoItem){viewHolder.select.setBackgroundResource(photoItem.isSelect() ? R.drawable.bg_album_pressed : R.drawable.bg_album);viewHolder.select.setText(photoItem.isSelect() ? String.valueOf(getSelectIndex(photoItem)) : "");viewHolder.select.setAlpha(photoItem.isSelect() ? 1.0f : 0.8f);}//更新所有照片索引private void setAllSelectBackground(){for(int i=0;i<selectItemList.size();i++){View view = mRecyclerView.findViewWithTag(selectItemList.get(i).getPath());if(view!=null){((TextView)view).setText(String.valueOf(i+1));}}}//获取选中图片索引private int getSelectIndex(PhotoItem photoItem){for(int i=0;i<selectItemList.size();i++){if(selectItemList.get(i).getPath().equals(photoItem.getPath()))return i+1;}return -1;}//删除选中图片private void removeSelectItem(PhotoItem photoItem){for(int i=0;i<selectItemList.size();i++){if(selectItemList.get(i).getPath().equals(photoItem.getPath())){selectItemList.remove(i);break;}}}@Overridepublic int getItemCount() {return photoItemList.size();}static class AlbumViewHolder extends RecyclerView.ViewHolder{private ImageView photo;private TextView select;public AlbumViewHolder(View view) {super(view);photo = (ImageView)view.findViewById(R.id.item_photo);select = (TextView)view.findViewById(R.id.item_text);}}
}

CameraAdapter(RepairActivity) 实例选中图片列表适配器

public class CameraAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements DragItemTouchHelperAdapter{final int VIEW_TYPE_PHOTO = 1;final int VIEW_TYPE_CAMERA = 0;private boolean pressed = false;private int photoLayout[];private RecyclerView mRecyclerView;private List<PhotoItem> photoItemList;private OnItemClickListener onItemClickListener;private Context mContext;public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}public CameraAdapter(RecyclerView recyclerView, List<PhotoItem> photoItemList, int[] photoLayout){this.mRecyclerView = recyclerView;this.photoItemList = photoItemList;this.photoLayout = photoLayout;this.mContext = recyclerView.getContext();}public void OnItemSelected() {if(!pressed){Vibrator vibrator = (Vibrator)mContext.getSystemService(mContext.VIBRATOR_SERVICE);vibrator.vibrate(500);pressed = true;for(int i=0;i<photoItemList.size()-1;i++){View view = mRecyclerView.findViewWithTag(i);if(view!=null){view.setVisibility(View.VISIBLE);}                }}}@Overridepublic int getItemViewType(int position) {if(position==photoItemList.size()-1)return VIEW_TYPE_CAMERA;elsereturn VIEW_TYPE_PHOTO;}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(mContext).inflate(viewType==0?R.layout.repair_list_camera:R.layout.repair_list_photo, parent, false);RecyclerView.ViewHolder viewHolder = viewType==0?new CameraVieHolder(view):new PhotoViewHolder(view);return viewHolder;}@Overridepublic void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {if(holder.getItemViewType()==0){CameraVieHolder viewHolder = (CameraVieHolder)holder;viewHolder.camera.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {onItemClickListener.onItemClick(holder,holder.getAdapterPosition());}});}else{PhotoViewHolder viewHolder = (PhotoViewHolder)holder;Bitmap bitmap = CacheManager.get(photoItemList.get(position).getPath());if(bitmap==null){bitmap=ImageHelper.getSmallCropBitmap(photoItemList.get(position).getPath(), photoLayout[0],photoLayout[1]);CacheManager.put(photoItemList.get(position).getPath(),ImageHelper.getSmallCropBitmap(photoItemList.get(position).getPath(), photoLayout[0],photoLayout[1]));}viewHolder.photo.setImageBitmap(bitmap);viewHolder.delete.setTag(position);viewHolder.delete.setVisibility(pressed?View.VISIBLE:View.GONE);viewHolder.delete.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {onItemDismiss(holder.getAdapterPosition());}});viewHolder.photo.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {onItemClickListener.onItemClick(holder,holder.getAdapterPosition());}});}}@Overridepublic void onItemDismiss(int position) {photoItemList.remove(position);notifyItemRemoved(position);}@Overridepublic boolean onItemMove(int fromPosition, int toPosition) {Collections.swap(photoItemList, fromPosition, toPosition);notifyItemMoved(fromPosition, toPosition);return true;}@Overridepublic int getItemCount() {return photoItemList.size();}class PhotoViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder{RelativeLayout container;ImageView photo;LinearLayout delete;public PhotoViewHolder(View view) {super(view);container = (RelativeLayout)view.findViewById(R.id.item_container);photo = (ImageView)view.findViewById(R.id.item_photo);delete = (LinearLayout)view.findViewById(R.id.item_delete);}//选中放大动画@Overridepublic void onItemSelected() {RelativeLayout container = (RelativeLayout)itemView.findViewById(R.id.item_container);Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.enlarge);container.startAnimation(animation);OnItemSelected();}//取消缩小动画@Overridepublic void onItemClear() {RelativeLayout container = (RelativeLayout)itemView.findViewById(R.id.item_container);Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.narrow);container.startAnimation(animation);}}class CameraVieHolder extends RecyclerView.ViewHolder{LinearLayout camera;public CameraVieHolder(View view){super(view);camera = (LinearLayout)view.findViewById(R.id.item_camera);}}
}

DragItemTouchHelperCallback 拖拽排序动画

public class DragItemTouchHelperCallback extends ItemTouchHelper.Callback{private DragItemTouchHelperAdapter mAdapter;public DragItemTouchHelperCallback(DragItemTouchHelperAdapter mAdapter){this.mAdapter = mAdapter;}@Overridepublic boolean isLongPressDragEnabled() {return true;}@Overridepublic boolean isItemViewSwipeEnabled() {return false;}@Overridepublic int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {if(viewHolder.getItemViewType()>0){final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;return makeMovementFlags(dragFlags, 0);}elsereturn makeMovementFlags(0,0);//0 滑动禁止}@Overridepublic boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {if (source.getItemViewType() != target.getItemViewType()) {return false;}// Notify the adapter of the movemAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());return true;}@Overridepublic void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {if (viewHolder.getItemViewType()>0) {ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;itemViewHolder.onItemSelected();}}super.onSelectedChanged(viewHolder, actionState);}@Overridepublic void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {super.clearView(recyclerView, viewHolder);if (viewHolder.getItemViewType()>0) {ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;itemViewHolder.onItemClear();}}@Overridepublic void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}
}

DragItemTouchHelperAdapter 选中图片拖拽,删除接口

public interface DragItemTouchHelperAdapter {boolean onItemMove(int fromPosition, int toPosition);void onItemDismiss(int position);
}

ItemTouchHelperViewHolder 选中图片长按,失去焦点接口

public interface ItemTouchHelperViewHolder {void onItemSelected();void onItemClear();
}

OnItemClickListener 相册子项选中接口

public interface OnItemClickListener {void onItemClick(RecyclerView.ViewHolder viewHolder,int position);
}

ScreenHelper 屏幕宽度

public class ScreenHelper {/*** dp转px* @param context* @param dp* @return*/public static int dp2px(Context context,float dp){return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());}/*** 获得屏幕宽度** @param context* @return*/public static int getScreenWidth(Context context){WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE );DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);return outMetrics .widthPixels;}
}

DiskLruCache 核心缓存类(第三方开源),DiskLruCacheManager 文件缓存管理器,LruCacheManager内存缓存管理器,CacheManager 文件+内存管理器

public class CacheManager {/*** 只使用内存缓存(LruCache)*/public static final int ONLY_LRU=1;/*** 只使用硬盘缓存(DiskLruCache)*/public static final int ONLY_DISKLRU=2;/*** 同时使用内存缓存(LruCache)与硬盘缓存(DiskLruCache)*/public static final int ALL_ALLOW=0;/*** 设置类型为硬盘缓存——用于取硬盘缓存大小*/public static final int DISKSIZE=0;/*** 设置类型为内存缓存——用于取内存缓存大小*/public static final int MEMORYSIZE=1;//设置硬盘缓存的最大值,单位为Mprivate static int maxSizeForDiskLruCache=0;//设置内存缓存的最大值,单位为Mprivate static int maxMemoryForLruCache=0;//设置自定义的硬盘缓存文件夹名称private static String dirNameForDiskLruCache="";//记录硬盘缓存与内存缓存起效标志private static int model=0;//硬盘缓存管理类private static DiskLruCacheManager diskLruCacheManager;//内存缓存管理类private static LruCacheManager lruCacheManager;private static Context ct;/*** 初始化缓存管理* @param context 上下文*/public static void init(Context context){ct=context;init_();}//根据传入的标志,初始化内存缓存以及硬盘缓存,默认开启是同时使用private static void init_(){switch (model) {case ALL_ALLOW:initDiskLruCacheManager();initLruCacheManager();break;case ONLY_LRU:initLruCacheManager();break;case ONLY_DISKLRU:initDiskLruCacheManager();break;default:break;}}//初始化内存缓存管理private static void initLruCacheManager(){if(maxMemoryForLruCache>0){lruCacheManager=new LruCacheManager(maxMemoryForLruCache);}else {lruCacheManager=new LruCacheManager();}}//初始化硬盘缓存管理private static void initDiskLruCacheManager(){if(maxSizeForDiskLruCache>0&&!TextUtils.isEmpty(dirNameForDiskLruCache)){diskLruCacheManager=new DiskLruCacheManager(ct,dirNameForDiskLruCache,maxSizeForDiskLruCache*1024*1024);}else if(maxSizeForDiskLruCache>0){diskLruCacheManager=new DiskLruCacheManager(ct, maxSizeForDiskLruCache*1024*1024);}else if(!TextUtils.isEmpty(dirNameForDiskLruCache)){diskLruCacheManager=new DiskLruCacheManager(ct, dirNameForDiskLruCache);}else {diskLruCacheManager=new DiskLruCacheManager(ct);}}/*** 设置硬盘缓存的最大值,单位为兆(M).* @param maxSizeForDisk 硬盘缓存最大值,单位为兆(M)*/public static void setMaxSize(int maxSizeForDisk){maxSizeForDiskLruCache=maxSizeForDisk;}/*** 设置内存缓存的最大值,单位为兆(M).* @param maxMemory 内存缓存最大值,单位为兆(M)*/public static void setMaxMemory(int maxMemory){maxMemoryForLruCache=maxMemory;}/*** 设置硬盘缓存自定义的文件名* @param dirName 自定义文件名*/public static void setDirName(String dirName){dirNameForDiskLruCache=dirName;}/*** 索引key对应的bitmap写入缓存* @param key 缓存索引* @param bitmap bitmap格式数据*/public static void put(String key,Bitmap bitmap){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){//设置硬盘缓存成功后,再设置内存缓存if(diskLruCacheManager.putDiskCache(key,bitmap)){lruCacheManager.putCache(key, bitmap);}}break;case ONLY_LRU:if(lruCacheManager!=null){lruCacheManager.putCache(key, bitmap);}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.putDiskCache(key,bitmap);}break;default:break;}}/*** 获取索引key对应的缓存内容* @param key 缓存索引key* @return  key索引对应的Bitmap数据*/public static Bitmap get(String key){Bitmap bitmap=null;switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){bitmap=lruCacheManager.getCache(key);if(bitmap==null){//如果硬盘缓存内容存在,内存缓存不存在。则在获取硬盘缓存后,将内容写入内存缓存bitmap=diskLruCacheManager.getDiskCache(key);lruCacheManager.putCache(key, bitmap);}}break;case ONLY_LRU:if(lruCacheManager!=null){bitmap=lruCacheManager.getCache(key);}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){bitmap=diskLruCacheManager.getDiskCache(key);}break;default:break;}return bitmap;}/*** 删除所有缓存*/public static void delete(){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){lruCacheManager.deleteCache();diskLruCacheManager.deleteDiskCache();}break;case ONLY_LRU:if(lruCacheManager!=null){lruCacheManager.deleteCache();}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.deleteDiskCache();}break;default:break;}}/*** 移除一条索引key对应的缓存* @param key 索引*/public  static void remove(String key){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){lruCacheManager.removeCache(key);diskLruCacheManager.removeDiskCache(key);}break;case ONLY_LRU:if(lruCacheManager!=null){lruCacheManager.removeCache(key);}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.removeDiskCache(key);}break;default:break;}}/*** 缓存数据同步*/public static void flush(){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){diskLruCacheManager.fluchCache();}break;case ONLY_LRU:break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.fluchCache();}break;default:break;}}/*** 设置缓存模式* @param modelSet ONLY_LRU、ONLY_DISK、ALL_ALLOW*/public static void setCacheModel(int modelSet){model=modelSet;}/*** 删除特定文件名的缓存文件* @param dirName 文件名*/public static void deleteFile(String dirName){if(diskLruCacheManager!=null){diskLruCacheManager.deleteFile(ct, dirName);}}/*** 获取缓存大小——内存缓存+硬盘缓存* @return*/public static int size(){int size=0;if(diskLruCacheManager!=null){size+=diskLruCacheManager.size();}if(lruCacheManager!=null){size+=lruCacheManager.size();}return size;}/*** 获取缓存大小* @param type 硬盘缓存类型:DISKSIZE、内存缓存类型:MEMORYSIZE* @return  对应类型的缓存大小*/public static int size(int type){int size=0;switch (type) {case DISKSIZE:if(diskLruCacheManager!=null){size+=diskLruCacheManager.size();}break;case MEMORYSIZE:if(lruCacheManager!=null){size+=lruCacheManager.size();}break;default:break;}return size;}/*** 关闭缓存*/public static void close(){if(diskLruCacheManager!=null){diskLruCacheManager.close();}}
}public  class DiskLruCacheManager {private static int maxSize=100*1024*1024;private  DiskLruCache mDiskLruCache;private final static String mImageCacheName="ImageCache";public DiskLruCacheManager(Context context){this(context, mImageCacheName, maxSize);}public DiskLruCacheManager(Context context,int maxDiskLruCacheSize){this(context, mImageCacheName, maxDiskLruCacheSize);}public DiskLruCacheManager(Context context,String dirName){this(context, dirName, maxSize);}public DiskLruCacheManager(Context context,String dirName,int maxDiskLruCacheSize){try {mDiskLruCache=DiskLruCache.open(getDiskCacheFile(context,dirName), getAppVersion(context), 1, maxDiskLruCacheSize);} catch (IOException e) {e.printStackTrace();}}/*** 获取文件夹地址,如果不存在,则创建* @param context 上下文* @param dirName 文件名* @return  File 文件*/private  File getDiskCacheFile(Context context,String dirName){File cacheDir=packDiskCacheFile(context,dirName);if (!cacheDir.exists()) {  cacheDir.mkdirs();  }  return cacheDir;  }/*** 获取文件夹地址* @param context 上下文* @param dirName 文件名* @return File 文件*/private  File packDiskCacheFile(Context context,String dirName){String cachePath;  if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  || !Environment.isExternalStorageRemovable()) {  cachePath = context.getExternalCacheDir().getPath();  } else {  cachePath = context.getCacheDir().getPath();  }  return new File(cachePath + File.separator + dirName);  }/** * 获取当前应用程序的版本号。 */  private int getAppVersion(Context context) {  try {  PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0);  return info.versionCode;  } catch (NameNotFoundException e) {  e.printStackTrace();  }  return 1;  }  /** * 使用MD5算法对传入的key进行加密并返回。 */  private String Md5(String key) {  String cacheKey;  try {  final MessageDigest mDigest = MessageDigest.getInstance("MD5");  mDigest.update(key.getBytes());  cacheKey = bytesToHexString(mDigest.digest());  } catch (NoSuchAlgorithmException e) {  cacheKey = String.valueOf(key.hashCode());  }  return cacheKey;  }  private String bytesToHexString(byte[] bytes) {  StringBuilder sb = new StringBuilder();  for (int i = 0; i < bytes.length; i++) {  String hex = Integer.toHexString(0xFF & bytes[i]);  if (hex.length() == 1) {  sb.append('0');  }  sb.append(hex);  }  return sb.toString();  }  /*** Bitmap格式数据写入到outputstream中 * @param bm Bitmap数据* @param baos outputstream* @return outputstream*/private OutputStream Bitmap2OutputStream(Bitmap bm,OutputStream baos) {  if(bm!=null){bm.compress(Bitmap.CompressFormat.JPEG, 80, baos);}return baos;}  /** * 将缓存记录同步到journal文件中。 */  public void fluchCache() {  if (mDiskLruCache != null) {  try {  mDiskLruCache.flush();  } catch (IOException e) {  e.printStackTrace();  }  }  }  /*** 获取硬盘缓存* @param key 所有* @return Bitmap格式缓存*/public Bitmap getDiskCache(String key){String md5Key=Md5(key);Bitmap bitmap=null;try {if(mDiskLruCache!=null){DiskLruCache.Snapshot snapshot = mDiskLruCache.get(md5Key);if(snapshot!=null){bitmap=BitmapFactory.decodeStream(snapshot.getInputStream(0)) ; }}}catch (IOException e) {e.printStackTrace();}return bitmap;}/*** 设置key对应的缓存* @param key 索引* @param bitmap Bitmap格式数据* @return 是否写入*/public boolean putDiskCache(String key,Bitmap bitmap){String md5Key=Md5(key);try {if(mDiskLruCache!=null){if(mDiskLruCache.get(md5Key)!=null){return true;}DiskLruCache.Editor editor=mDiskLruCache.edit(md5Key);if(editor!=null){OutputStream outputStream= editor.newOutputStream(0);Bitmap2OutputStream(bitmap,outputStream);if(outputStream!=null){editor.commit();return true;}else {editor.abort();return false;}}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return false;}public void deleteDiskCache(){try {if(mDiskLruCache!=null){mDiskLruCache.delete();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public void removeDiskCache(String key){if(mDiskLruCache!=null){try {mDiskLruCache.remove(key);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public  void deleteFile(Context context,String dirName){try {DiskLruCache.deleteContents(packDiskCacheFile(context,dirName));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public int size(){int size=0;if(mDiskLruCache!=null){size=(int) mDiskLruCache.size();}return size;}public void close(){if(mDiskLruCache!=null){try {mDiskLruCache.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}public class LruCacheManager {private LruCache<String, Bitmap> lruCache;public LruCacheManager(){this((int)Runtime.getRuntime().maxMemory()/1024/8);}//设置自定义大小的LruCachepublic LruCacheManager(int maxSize){lruCache=new LruCache<String, Bitmap>(maxSize*1024){@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount()/1024;}};}/*** 写入索引key对应的缓存* @param key 索引* @param bitmap 缓存内容* @return 写入结果*/public Bitmap putCache(String key,Bitmap bitmap){Bitmap bitmapValue=getCache(key);if(bitmapValue==null){if(lruCache!=null&&bitmap!=null)bitmapValue= lruCache.put(key, bitmap);}return bitmapValue;}/*** 获取缓存* @param key 索引key对应的缓存* @return  缓存*/public Bitmap getCache(String key){ if(lruCache!=null){return lruCache.get(key);}return null;}public void deleteCache(){if(lruCache!=null)lruCache.evictAll();}public void removeCache(String key){if(lruCache!=null)lruCache.remove(key);}public int size(){int size=0;if(lruCache!=null)size+=lruCache.size();return size;}
}public final class DiskLruCache implements Closeable {static final String JOURNAL_FILE = "journal";static final String JOURNAL_FILE_TMP = "journal.tmp";static final String MAGIC = "libcore.io.DiskLruCache";static final String VERSION_1 = "1";static final long ANY_SEQUENCE_NUMBER = -1;private static final String CLEAN = "CLEAN";private static final String DIRTY = "DIRTY";private static final String REMOVE = "REMOVE";private static final String READ = "READ";private static final Charset UTF_8 = Charset.forName("UTF-8");private static final int IO_BUFFER_SIZE = 8 * 1024;/** This cache uses a journal file named "journal". A typical journal file* looks like this:*     libcore.io.DiskLruCache*     1*     100*     2**     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054*     DIRTY 335c4c6028171cfddfbaae1a9c313c52*     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342*     REMOVE 335c4c6028171cfddfbaae1a9c313c52*     DIRTY 1ab96a171faeeee38496d8b330771a7a*     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234*     READ 335c4c6028171cfddfbaae1a9c313c52*     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6** The first five lines of the journal form its header. They are the* constant string "libcore.io.DiskLruCache", the disk cache's version,* the application's version, the value count, and a blank line.** Each of the subsequent lines in the file is a record of the state of a* cache entry. Each line contains space-separated values: a state, a key,* and optional state-specific values.*   o DIRTY lines track that an entry is actively being created or updated.*     Every successful DIRTY action should be followed by a CLEAN or REMOVE*     action. DIRTY lines without a matching CLEAN or REMOVE indicate that*     temporary files may need to be deleted.*   o CLEAN lines track a cache entry that has been successfully published*     and may be read. A publish line is followed by the lengths of each of*     its values.*   o READ lines track accesses for LRU.*   o REMOVE lines track entries that have been deleted.** The journal file is appended to as cache operations occur. The journal may* occasionally be compacted by dropping redundant lines. A temporary file named* "journal.tmp" will be used during compaction; that file should be deleted if* it exists when the cache is opened.*/private final File directory;private final File journalFile;private final File journalFileTmp;private final int appVersion;private final long maxSize;private final int valueCount;private long size = 0;private Writer journalWriter;private final LinkedHashMap<String, Entry> lruEntries= new LinkedHashMap<String, Entry>(0, 0.75f, true);private int redundantOpCount;/*** To differentiate between old and current snapshots, each entry is given* a sequence number each time an edit is committed. A snapshot is stale if* its sequence number is not equal to its entry's sequence number.*/private long nextSequenceNumber = 0;/* From java.util.Arrays */@SuppressWarnings("unchecked")private static <T> T[] copyOfRange(T[] original, int start, int end) {final int originalLength = original.length; // For exception priority compatibility.if (start > end) {throw new IllegalArgumentException();}if (start < 0 || start > originalLength) {throw new ArrayIndexOutOfBoundsException();}final int resultLength = end - start;final int copyLength = Math.min(resultLength, originalLength - start);final T[] result = (T[]) Array.newInstance(original.getClass().getComponentType(), resultLength);System.arraycopy(original, start, result, 0, copyLength);return result;}/*** Returns the remainder of 'reader' as a string, closing it when done.*/public static String readFully(Reader reader) throws IOException {try {StringWriter writer = new StringWriter();char[] buffer = new char[1024];int count;while ((count = reader.read(buffer)) != -1) {writer.write(buffer, 0, count);}return writer.toString();} finally {reader.close();}}/*** Returns the ASCII characters up to but not including the next "\r\n", or* "\n".** @throws EOFException if the stream is exhausted before the next newline*     character.*/public static String readAsciiLine(InputStream in) throws IOException {// TODO: support UTF-8 here insteadStringBuilder result = new StringBuilder(80);while (true) {int c = in.read();if (c == -1) {throw new EOFException();} else if (c == '\n') {break;}result.append((char) c);}int length = result.length();if (length > 0 && result.charAt(length - 1) == '\r') {result.setLength(length - 1);}return result.toString();}/*** Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.*/public static void closeQuietly(Closeable closeable) {if (closeable != null) {try {closeable.close();} catch (RuntimeException rethrown) {throw rethrown;} catch (Exception ignored) {}}}/*** Recursively delete everything in {@code dir}.*/// TODO: this should specify paths as Strings rather than as Filespublic static void deleteContents(File dir) throws IOException {File[] files = dir.listFiles();if (files == null) {throw new IllegalArgumentException("not a directory: " + dir);}for (File file : files) {if (file.isDirectory()) {deleteContents(file);}if (!file.delete()) {throw new IOException("failed to delete file: " + file);}}}/** This cache uses a single background thread to evict entries. */private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());private final Callable<Void> cleanupCallable = new Callable<Void>() {@Override public Void call() throws Exception {synchronized (DiskLruCache.this) {if (journalWriter == null) {return null; // closed}trimToSize();if (journalRebuildRequired()) {rebuildJournal();redundantOpCount = 0;}}return null;}};private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {this.directory = directory;this.appVersion = appVersion;this.journalFile = new File(directory, JOURNAL_FILE);this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);this.valueCount = valueCount;this.maxSize = maxSize;}/*** Opens the cache in {@code directory}, creating a cache if none exists* there.** @param directory a writable directory* @param appVersion* @param valueCount the number of values per cache entry. Must be positive.* @param maxSize the maximum number of bytes this cache should use to store* @throws IOException if reading or writing the cache directory fails*/public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)throws IOException {if (maxSize <= 0) {throw new IllegalArgumentException("maxSize <= 0");}if (valueCount <= 0) {throw new IllegalArgumentException("valueCount <= 0");}// prefer to pick up where we left offDiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);if (cache.journalFile.exists()) {try {cache.readJournal();cache.processJournal();cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),IO_BUFFER_SIZE);return cache;} catch (IOException journalIsCorrupt) {
//                System.logW("DiskLruCache " + directory + " is corrupt: "
//                        + journalIsCorrupt.getMessage() + ", removing");cache.delete();}}// create a new empty cachedirectory.mkdirs();cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);cache.rebuildJournal();return cache;}private void readJournal() throws IOException {InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);try {String magic = readAsciiLine(in);String version = readAsciiLine(in);String appVersionString = readAsciiLine(in);String valueCountString = readAsciiLine(in);String blank = readAsciiLine(in);if (!MAGIC.equals(magic)|| !VERSION_1.equals(version)|| !Integer.toString(appVersion).equals(appVersionString)|| !Integer.toString(valueCount).equals(valueCountString)|| !"".equals(blank)) {throw new IOException("unexpected journal header: ["+ magic + ", " + version + ", " + valueCountString + ", " + blank + "]");}while (true) {try {readJournalLine(readAsciiLine(in));} catch (EOFException endOfJournal) {break;}}} finally {closeQuietly(in);}}private void readJournalLine(String line) throws IOException {String[] parts = line.split(" ");if (parts.length < 2) {throw new IOException("unexpected journal line: " + line);}String key = parts[1];if (parts[0].equals(REMOVE) && parts.length == 2) {lruEntries.remove(key);return;}Entry entry = lruEntries.get(key);if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);}if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {entry.readable = true;entry.currentEditor = null;entry.setLengths(copyOfRange(parts, 2, parts.length));} else if (parts[0].equals(DIRTY) && parts.length == 2) {entry.currentEditor = new Editor(entry);} else if (parts[0].equals(READ) && parts.length == 2) {// this work was already done by calling lruEntries.get()} else {throw new IOException("unexpected journal line: " + line);}}/*** Computes the initial size and collects garbage as a part of opening the* cache. Dirty entries are assumed to be inconsistent and will be deleted.*/private void processJournal() throws IOException {deleteIfExists(journalFileTmp);for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {Entry entry = i.next();if (entry.currentEditor == null) {for (int t = 0; t < valueCount; t++) {size += entry.lengths[t];}} else {entry.currentEditor = null;for (int t = 0; t < valueCount; t++) {deleteIfExists(entry.getCleanFile(t));deleteIfExists(entry.getDirtyFile(t));}i.remove();}}}/*** Creates a new journal that omits redundant information. This replaces the* current journal if it exists.*/private synchronized void rebuildJournal() throws IOException {if (journalWriter != null) {journalWriter.close();}Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);writer.write(MAGIC);writer.write("\n");writer.write(VERSION_1);writer.write("\n");writer.write(Integer.toString(appVersion));writer.write("\n");writer.write(Integer.toString(valueCount));writer.write("\n");writer.write("\n");for (Entry entry : lruEntries.values()) {if (entry.currentEditor != null) {writer.write(DIRTY + ' ' + entry.key + '\n');} else {writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');}}writer.close();journalFileTmp.renameTo(journalFile);journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);}private static void deleteIfExists(File file) throws IOException {
//        try {//            Libcore.os.remove(file.getPath());
//        } catch (ErrnoException errnoException) {//            if (errnoException.errno != OsConstants.ENOENT) {//                throw errnoException.rethrowAsIOException();
//            }
//        }if (file.exists() && !file.delete()) {throw new IOException();}}/*** Returns a snapshot of the entry named {@code key}, or null if it doesn't* exist is not currently readable. If a value is returned, it is moved to* the head of the LRU queue.*/public synchronized Snapshot get(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null) {return null;}if (!entry.readable) {return null;}/** Open all streams eagerly to guarantee that we see a single published* snapshot. If we opened streams lazily then the streams could come* from different edits.*/InputStream[] ins = new InputStream[valueCount];try {for (int i = 0; i < valueCount; i++) {ins[i] = new FileInputStream(entry.getCleanFile(i));}} catch (FileNotFoundException e) {// a file must have been deleted manually!return null;}redundantOpCount++;journalWriter.append(READ + ' ' + key + '\n');if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return new Snapshot(key, entry.sequenceNumber, ins);}/*** Returns an editor for the entry named {@code key}, or null if another* edit is in progress.*/public Editor edit(String key) throws IOException {return edit(key, ANY_SEQUENCE_NUMBER);}private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER&& (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {return null; // snapshot is stale}if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);} else if (entry.currentEditor != null) {return null; // another edit is in progress}Editor editor = new Editor(entry);entry.currentEditor = editor;// flush the journal before creating files to prevent file leaksjournalWriter.write(DIRTY + ' ' + key + '\n');journalWriter.flush();return editor;}/*** Returns the directory where this cache stores its data.*/public File getDirectory() {return directory;}/*** Returns the maximum number of bytes that this cache should use to store* its data.*/public long maxSize() {return maxSize;}/*** Returns the number of bytes currently being used to store the values in* this cache. This may be greater than the max size if a background* deletion is pending.*/public synchronized long size() {return size;}private synchronized void completeEdit(Editor editor, boolean success) throws IOException {Entry entry = editor.entry;if (entry.currentEditor != editor) {throw new IllegalStateException();}// if this edit is creating the entry for the first time, every index must have a valueif (success && !entry.readable) {for (int i = 0; i < valueCount; i++) {if (!entry.getDirtyFile(i).exists()) {editor.abort();throw new IllegalStateException("edit didn't create file " + i);}}}for (int i = 0; i < valueCount; i++) {File dirty = entry.getDirtyFile(i);if (success) {if (dirty.exists()) {File clean = entry.getCleanFile(i);dirty.renameTo(clean);long oldLength = entry.lengths[i];long newLength = clean.length();entry.lengths[i] = newLength;size = size - oldLength + newLength;}} else {deleteIfExists(dirty);}}redundantOpCount++;entry.currentEditor = null;if (entry.readable | success) {entry.readable = true;journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');if (success) {entry.sequenceNumber = nextSequenceNumber++;}} else {lruEntries.remove(entry.key);journalWriter.write(REMOVE + ' ' + entry.key + '\n');}if (size > maxSize || journalRebuildRequired()) {executorService.submit(cleanupCallable);}}/*** We only rebuild the journal when it will halve the size of the journal* and eliminate at least 2000 ops.*/private boolean journalRebuildRequired() {final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD&& redundantOpCount >= lruEntries.size();}/*** Drops the entry for {@code key} if it exists and can be removed. Entries* actively being edited cannot be removed.** @return true if an entry was removed.*/public synchronized boolean remove(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null || entry.currentEditor != null) {return false;}for (int i = 0; i < valueCount; i++) {File file = entry.getCleanFile(i);if (!file.delete()) {throw new IOException("failed to delete " + file);}size -= entry.lengths[i];entry.lengths[i] = 0;}redundantOpCount++;journalWriter.append(REMOVE + ' ' + key + '\n');lruEntries.remove(key);if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return true;}/*** Returns true if this cache has been closed.*/public boolean isClosed() {return journalWriter == null;}private void checkNotClosed() {if (journalWriter == null) {throw new IllegalStateException("cache is closed");}}/*** Force buffered operations to the filesystem.*/public synchronized void flush() throws IOException {checkNotClosed();trimToSize();journalWriter.flush();}/*** Closes this cache. Stored values will remain on the filesystem.*/public synchronized void close() throws IOException {if (journalWriter == null) {return; // already closed}for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {if (entry.currentEditor != null) {entry.currentEditor.abort();}}trimToSize();journalWriter.close();journalWriter = null;}private void trimToSize() throws IOException {while (size > maxSize) {
//            Map.Entry<String, Entry> toEvict = lruEntries.eldest();final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();remove(toEvict.getKey());}}/*** Closes the cache and deletes all of its stored values. This will delete* all files in the cache directory including files that weren't created by* the cache.*/public void delete() throws IOException {close();deleteContents(directory);}private void validateKey(String key) {if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {throw new IllegalArgumentException("keys must not contain spaces or newlines: \"" + key + "\"");}}private static String inputStreamToString(InputStream in) throws IOException {return readFully(new InputStreamReader(in, UTF_8));}/*** A snapshot of the values for an entry.*/public final class Snapshot implements Closeable {private final String key;private final long sequenceNumber;private final InputStream[] ins;private Snapshot(String key, long sequenceNumber, InputStream[] ins) {this.key = key;this.sequenceNumber = sequenceNumber;this.ins = ins;}/*** Returns an editor for this snapshot's entry, or null if either the* entry has changed since this snapshot was created or if another edit* is in progress.*/public Editor edit() throws IOException {return DiskLruCache.this.edit(key, sequenceNumber);}/*** Returns the unbuffered stream with the value for {@code index}.*/public InputStream getInputStream(int index) {return ins[index];}/*** Returns the string value for {@code index}.*/public String getString(int index) throws IOException {return inputStreamToString(getInputStream(index));}@Override public void close() {for (InputStream in : ins) {closeQuietly(in);}}}/*** Edits the values for an entry.*/public final class Editor {private final Entry entry;private boolean hasErrors;private Editor(Entry entry) {this.entry = entry;}/*** Returns an unbuffered input stream to read the last committed value,* or null if no value has been committed.*/public InputStream newInputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}if (!entry.readable) {return null;}return new FileInputStream(entry.getCleanFile(index));}}/*** Returns the last committed value as a string, or null if no value* has been committed.*/public String getString(int index) throws IOException {InputStream in = newInputStream(index);return in != null ? inputStreamToString(in) : null;}/*** Returns a new unbuffered output stream to write the value at* {@code index}. If the underlying output stream encounters errors* when writing to the filesystem, this edit will be aborted when* {@link #commit} is called. The returned output stream does not throw* IOExceptions.*/public OutputStream newOutputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));}}/*** Sets the value at {@code index} to {@code value}.*/public void set(int index, String value) throws IOException {Writer writer = null;try {writer = new OutputStreamWriter(newOutputStream(index), UTF_8);writer.write(value);} finally {closeQuietly(writer);}}/*** Commits this edit so it is visible to readers.  This releases the* edit lock so another edit may be started on the same key.*/public void commit() throws IOException {if (hasErrors) {completeEdit(this, false);remove(entry.key); // the previous entry is stale} else {completeEdit(this, true);}}/*** Aborts this edit. This releases the edit lock so another edit may be* started on the same key.*/public void abort() throws IOException {completeEdit(this, false);}private class FaultHidingOutputStream extends FilterOutputStream {private FaultHidingOutputStream(OutputStream out) {super(out);}@Override public void write(int oneByte) {try {out.write(oneByte);} catch (IOException e) {hasErrors = true;}}@Override public void write(byte[] buffer, int offset, int length) {try {out.write(buffer, offset, length);} catch (IOException e) {hasErrors = true;}}@Override public void close() {try {out.close();} catch (IOException e) {hasErrors = true;}}@Override public void flush() {try {out.flush();} catch (IOException e) {hasErrors = true;}}}}private final class Entry {private final String key;/** Lengths of this entry's files. */private final long[] lengths;/** True if this entry has ever been published */private boolean readable;/** The ongoing edit or null if this entry is not being edited. */private Editor currentEditor;/** The sequence number of the most recently committed edit to this entry. */private long sequenceNumber;private Entry(String key) {this.key = key;this.lengths = new long[valueCount];}public String getLengths() throws IOException {StringBuilder result = new StringBuilder();for (long size : lengths) {result.append(' ').append(size);}return result.toString();}/*** Set lengths using decimal numbers like "10123".*/private void setLengths(String[] strings) throws IOException {if (strings.length != valueCount) {throw invalidLengths(strings);}try {for (int i = 0; i < strings.length; i++) {lengths[i] = Long.parseLong(strings[i]);}} catch (NumberFormatException e) {throw invalidLengths(strings);}}private IOException invalidLengths(String[] strings) throws IOException {throw new IOException("unexpected journal line: " + Arrays.toString(strings));}public File getCleanFile(int i) {return new File(directory, key + "." + i);}public File getDirtyFile(int i) {return new File(directory, key + "." + i + ".tmp");}}
}

CameraDialogFragment 弹出菜单

public class CameraDialogFragment extends DialogFragment {private OnItemClickListener onItemClickListener;public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);}@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {//屏幕外退出getDialog().setCanceledOnTouchOutside(true);//背景透明getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));Window window = getDialog().getWindow();WindowManager.LayoutParams wlp = window.getAttributes();wlp.gravity = Gravity.BOTTOM;window.setAttributes(wlp);View view = inflater.inflate(R.layout.fragment_camera,null);TextView camera = (TextView)view.findViewById(R.id.camera);TextView album = (TextView)view.findViewById(R.id.album);TextView cancel = (TextView)view.findViewById(R.id.cancel);camera.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();onItemClickListener.onItemClick(null,0);}});album.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();onItemClickListener.onItemClick(null,1);}});cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();}});return view;}
}

activity_repair.xml 实例布局页

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"tools:context="com.room.activity.RepairActivity"><RelativeLayout
        android:id="@id/tab_title"android:layout_width="match_parent"android:layout_height="50dp"android:background="@color/skyblue"android:paddingLeft="10dp"android:paddingRight="10dp"><ImageButton
            android:layout_centerVertical="true"android:background="@drawable/back"android:onClick="onBack"android:layout_width="30dp"android:layout_height="30dp" /><TextView
            android:layout_centerInParent="true"android:id="@id/tab_title_name"android:text="@string/repair"android:textSize="18dp"android:textColor="@color/white"android:gravity="center"android:layout_width="wrap_content"android:layout_height="match_parent" /><ImageButton
            android:layout_alignParentRight="true"android:background="@drawable/release"android:layout_centerVertical="true"android:alpha=".6"android:clickable="false"android:onClick="onSubmit"android:id="@id/submit"android:layout_width="30dp"android:layout_height="30dp" /></RelativeLayout><LinearLayout
        android:layout_marginTop="10dp"android:paddingLeft="10dp"android:paddingRight="10dp"android:orientation="vertical"android:layout_below="@id/tab_title"android:layout_width="match_parent"android:layout_height="wrap_content"><EditText
            android:id="@id/content"android:minHeight="100dp"android:textSize="16sp"android:background="@null"android:gravity="left|top"android:hint="@string/hint_repair"android:textColor="@color/black"android:layout_width="match_parent"android:layout_height="wrap_content" /><LinearLayout
            android:layout_marginTop="10dp"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><android.support.v7.widget.RecyclerView
                android:id="@id/list"android:layout_width="wrap_content"android:layout_height="wrap_content" /></LinearLayout><View
            android:layout_marginTop="10dp"android:background="@color/grey"android:layout_width="match_parent"android:layout_height="0.5dp"/></LinearLayout></RelativeLayout>

activity_album.xml 相册布局页

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"tools:context="com.room.activity.AlbumActivity"><RelativeLayoutandroid:id="@id/tab_title"android:layout_width="match_parent"android:layout_height="50dp"android:background="@color/skyblue"android:paddingLeft="10dp"android:paddingRight="10dp"><ImageButtonandroid:layout_centerVertical="true"android:background="@drawable/back"android:onClick="onBack"android:layout_width="30dp"android:layout_height="30dp" /><TextViewandroid:layout_centerInParent="true"android:id="@id/tab_title_name"android:text="@string/album"android:textSize="18dp"android:textColor="@color/white"android:gravity="center"android:layout_width="wrap_content"android:layout_height="match_parent" /><TextViewandroid:layout_alignParentRight="true"android:layout_centerVertical="true"android:text="@string/cancel"android:background="@color/transparent"android:textColor="@color/white"android:onClick="onCancel"android:textSize="18dp"android:gravity="center_vertical"android:id="@id/cancel"android:layout_width="wrap_content"android:layout_height="match_parent" /></RelativeLayout><android.support.v7.widget.RecyclerViewandroid:id="@id/list"android:paddingBottom="10dp"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:layout_below="@id/tab_title"android:layout_above="@id/container"android:layout_width="match_parent"android:layout_height="match_parent" /><RelativeLayoutandroid:id="@id/container"android:background="#f3f3f3"android:layout_alignParentBottom="true"android:layout_width="match_parent"android:layout_height="50dp"><Viewandroid:background="@color/grey"android:layout_width="match_parent"android:layout_height="0.5dp"/><Buttonandroid:id="@id/submit"android:text="@string/submit"android:clickable="false"android:onClick="onSubmit"android:textColor="@color/darkgray"android:background="@drawable/bg_album_submit"android:layout_centerVertical="true"android:layout_marginRight="10dp"android:alpha=".8"android:layout_alignParentRight="true"android:layout_width="70dp"android:layout_height="35dp" /></RelativeLayout>
</RelativeLayout>

repair_list_camera.xml 选中图片布局页

<?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="120dp"><LinearLayout
        android:layout_marginTop="10dp"android:layout_marginRight="10dp"android:id="@id/item_camera"android:orientation="vertical"android:background="#f2f2f2"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
            android:layout_marginTop="20dp"android:layout_gravity="center"android:background="@drawable/camera"android:layout_width="40dp"android:layout_height="40dp" /><TextView
            android:text="@string/camera"android:textSize="18sp"android:textColor="#cfcfcf"android:layout_marginTop="5dp"android:gravity="center"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
</RelativeLayout>

repair_list_photo.xml 拍照布局页

<?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="120dp" ><RelativeLayout
        android:layout_marginTop="10dp"android:layout_marginRight="10dp"android:id="@id/item_container"android:background="#f2f2f2"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
            android:id="@id/item_photo"android:layout_centerInParent="true"android:layout_width="match_parent"android:layout_height="match_parent" /><LinearLayout
            android:id="@id/item_delete"android:layout_alignParentRight="true"android:gravity="center"android:alpha=".8"android:background="@drawable/bg_repair_delete"android:layout_width="16dp"android:layout_height="16dp"><ImageView
                android:background="@drawable/x"android:layout_width="10dp"android:layout_height="10dp" /></LinearLayout></RelativeLayout>
</RelativeLayout>

fragment_camera.xml 弹出页布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayout
        android:background="@drawable/bg_fragment_album"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><TextView
            android:id="@id/camera"android:text="@string/camera"android:gravity="center"android:textColor="#0077f4"android:textSize="18dp"android:layout_width="match_parent"android:layout_height="50dp" /><View
            android:background="@color/darkgray"android:layout_width="match_parent"android:layout_height="0.5dp"/><TextView
            android:id="@id/album"android:text="@string/album"android:gravity="center"android:textColor="#0077f4"android:textSize="18dp"android:layout_width="match_parent"android:layout_height="50dp" /></LinearLayout><TextView
        android:layout_marginTop="10dp"android:background="@drawable/bg_fragment_album"android:id="@id/cancel"android:gravity="center"android:text="@string/cancel"android:textSize="18dp"android:layout_marginBottom="10dp"android:textColor="#0077f4"android:layout_width="match_parent"android:layout_height="50dp" /></LinearLayout>

album_list_photo.xml 相册子项布局文件

<?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="120dp"><RelativeLayout
        android:layout_marginRight="5dp"android:layout_marginTop="5dp"android:background="@color/grey"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
            android:id="@id/item_photo"android:layout_width="match_parent"android:layout_height="match_parent" /><TextView
            android:id="@id/item_text"android:layout_alignParentRight="true"android:layout_marginRight="3dp"android:layout_marginTop="3dp"android:alpha=".8"android:textColor="@color/white"android:textSize="16dp"android:gravity="center"android:background="@drawable/bg_album"android:layout_width="25dp"android:layout_height="25dp" /></RelativeLayout></RelativeLayout>

AndroidRecyclerView仿QQ相册功能相关推荐

  1. android 仿qq相册功能,Android第四十九期 - 仿QQ空间上传功能+本地数据库存储

    最近在看撸撸代码,他自己创了一种,网上有三种,分别是OpenDroid,greenDao,sugar,Sqlite原生写法,感觉都差不多,个人使用最优的是greenDao,下面开始介绍:    1.O ...

  2. 2020年7月win32 C\C++ API 写的仿QQ截图功能

    2020年7月win32 C\C++ API 写的仿QQ截图功能 近日,经常用到截图,但是没有QQ没有微信 的电脑上,截图非常不方便,起初打算网上随便找个类似的就算了,但是找了一下午,发现都是些很基础 ...

  3. Android学习之仿QQ侧滑功能的实现

    现在项目越来越多的应用了滑动删除的功能,Android本来遵循的是长按删除,IOS定制的是滑动删除,不可否认滑动删除确实在客户体验上要好一点,所以看了很多关于仿QQ滑动删除的例子,还是感觉代码家的An ...

  4. 用C++实现仿QQ屏幕截图功能,以后不登QQ也能截图!

    用C++实现的仿QQ屏幕静态截图的功能,可在屏幕的任何地方抓图或者截屏,从功能上来说和QQ的截图功能几乎没什么两样. 项目结构展示: 部分源码展示: 小编已把此功能写成了一个类,使用时调用就行了,简单 ...

  5. go语言实现仿QQ聊天功能

    1.实现原理 为了最大限度的减少服务器的负担,这里使用P2P模式实现仿QQ的聊天功能,服务器端和客户端职责如下: 1)服务器端:通过TCP方式实现客户端身份认证.向已经认证的用户推送好友信息.向其他登 ...

  6. Swing学习----------实现仿QQ注册功能

    在上一次的博客中实现了一个仿QQ的登录界面,现在将实现一个注册QQ账号的功能,在本文中将制作一个QQ注册的页面,然后通过一些swing中的组件来获取用户的信息,最后通过点击注册按钮将注册信息保存到My ...

  7. 仿QQ相册RecyclerView滑动选中

    重要的事情在前面说 本文介绍的方法是基于坐标动态计算该Item在RecyclerView中的行列值实现的,是我最初的设计思路,相对有局限性,后面借鉴RecyclerView的相关API进行了重新设计新 ...

  8. php实现qq相册功能,使用javascript如何实现QQ空间相册展示

    本文给大家分享基于javascript制作的qq空间相册展示效果,涉及到html\css布局思维,浮动定位详解,具体实现代码大家参考下本文 知识点:html/css布局思维,浮动/定位详解,大企业标准 ...

  9. 仿QQ 聊天 @输入功能

    QQAtInput 仿QQ at 功能 最近在做即时通讯功能,在开发之余,封装了一个仿QQ at 功能组件.可以用在聊天,评论等模块中.该组件主要解决两个难点问题: @选取的成员作为一个整体,不可编辑 ...

最新文章

  1. ​他被称为印度“ IT 大王”,富可敌国,却精打细算如守财奴
  2. 存储过程中引用的常规表,临时表以及表变量是否会导致存储过程的重编译
  3. 《Adobe Photoshop CS5中文版经典教程(全彩版)》—第1课1.2节使用工具
  4. gulp自动化ES6转ES5
  5. html 鼠标单击单元格,vue-easytable点击表格中某个单元格操作
  6. 树莓派静态IP配置方法
  7. bzoj1066 [SCOI2007]蜥蜴 网络流复制点模型
  8. 现代 CMake 简明教程(一)- CMake 基础
  9. java语言基础教程课后答案,积累总结
  10. 人生苦短 我用Python
  11. intel CPU详解
  12. Webx3 学习笔记
  13. 【项目简介】LinkWeChat:基于企业微信开源系统
  14. 王道操作系统2.1.7课后习题--解答题
  15. android+google+play,打开链接的google play商店在手机版android
  16. 手机无线如何共享给台式计算机,教你用手机做热点分享wifi给台式电脑用,不是用数据网络哦...
  17. Chapter 20-APIs(应用程序编程接口)
  18. Mac系统之----教你怎么显示隐藏文件,或者关闭显示隐藏文件
  19. [Watermelon_book] Chapter 3 Linear Model
  20. 手机界面设计-2——电量显示界面

热门文章

  1. svn 更新命令(冲突时使用theirs)
  2. 计算机初级培训教学大纲,计算机初级培训教学大纲(范文).doc
  3. 提取视频关键帧和关键帧的时间点信息
  4. 全球BT下载网站排名
  5. 一文读懂C#中的抽象类、抽象方法、virtual虚函数、override重写函数及父类子类构造函数和析构函数的执行顺序
  6. 倒计时1天! | 明日9点,这场精彩的Web3盛宴不容错过
  7. antd 表格设置动态列(动态表头)
  8. b. 《计算机软件保护条例》没有规定软件著作权人的改编权,自然人创作的享有著作权的计算机软件的权利保护期限为()。...
  9. 串口服务器直连路由器如何配置,塔石物联网:串口服务器如何实现跨网段传输!...
  10. 阐明Linux文件系统的潜藏权限