AndroidRecyclerView仿QQ相册功能

实例模仿新版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() {
            @Override
            public 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() {
                @Override
                public 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");
        }
        else
            dialogFragment.show(getFragmentManager(),"dialog");
    }

    @Override
    protected 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;

    @Override
    public void onSubmit(View view) {

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        init(R.layout.activity_repair);
        mEditText = (EditText)findViewById(R.id.content);
        mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public 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, // id
            MediaStore.Images.Media.BUCKET_ID, // dir id 目录
            MediaStore.Images.Media.BUCKET_DISPLAY_NAME, // dir name 目录名字
            MediaStore.Images.Media.DATA//路径
    };
    @Override
    protected 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() {
            @Override
            public void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {
                notifySubmit();
            }
        });

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                //停止滑动后缓存当前屏幕图片
                if(newState == 0){
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            for(int i=gridLayoutManager.findFirstVisibleItemPosition();i<gridLayoutManager.findLastVisibleItemPosition();i++){
                                cacheBitmap(i);
                            }
                        }
                    }).start();
                }
            }
        });

        mRecyclerView.setAdapter(mAdapter);

        handler = new Handler(new Handler.Callback() {
            @Override
            public 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() {
            @Override
            public void run() {
                for(int i=0;i<photoItemList.size();i++){
                    cacheBitmap(i);
                }
            }
        }).start();
    }

    //缓存bitmap
    public 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";

    @Override
    protected 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();
        }
        else
            initialUI();
    }

    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)); // set
            startActivityForResult(intent, FinalHelper.RequestCode_Camera);
        }
        else{
            Toast.makeText(this, "未发现SD卡或SD卡不可用", Toast.LENGTH_LONG).show();
            finish();
        }
    }

    //插入到系统相册
    private void insertToCamera(){
        new Thread(new Runnable() {
            @Override
            public 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();
    }

    @Override
    public 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);
        }
    }

    @Override
    public 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;
    }

    @Override
    public 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() {
            @Override
            public 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;
            }
        }
    }

    @Override
    public 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);
                }                
            }
        }
    }

    @Override
    public int getItemViewType(int position) {
        if(position==photoItemList.size()-1)
            return VIEW_TYPE_CAMERA;
        else
            return VIEW_TYPE_PHOTO;
    }

    @Override
    public 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;
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        if(holder.getItemViewType()==0){
            CameraVieHolder viewHolder = (CameraVieHolder)holder;
            viewHolder.camera.setOnClickListener(new View.OnClickListener() {
                @Override
                public 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() {
                @Override
                public void onClick(View v) {
                    onItemDismiss(holder.getAdapterPosition());
                }
            });
            viewHolder.photo.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onItemClickListener.onItemClick(holder,holder.getAdapterPosition());
                }
            });
        }
    }

    @Override
    public void onItemDismiss(int position) {
        photoItemList.remove(position);
        notifyItemRemoved(position);
    }

    @Override
    public boolean onItemMove(int fromPosition, int toPosition) {
        Collections.swap(photoItemList, fromPosition, toPosition);
        notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    @Override
    public 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);
        }

         //选中放大动画
        @Override
        public void onItemSelected() {
            RelativeLayout container = (RelativeLayout)itemView.findViewById(R.id.item_container);
            Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.enlarge);
            container.startAnimation(animation);
            OnItemSelected();
        }

        //取消缩小动画
        @Override
        public 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;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    @Override
    public 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);
        }
        else
            return makeMovementFlags(0,0);//0 滑动禁止
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
        if (source.getItemViewType() != target.getItemViewType()) {
            return false;
        }

        // Notify the adapter of the move
        mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public 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);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (viewHolder.getItemViewType()>0) {
            ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }

    @Override
    public 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;

    //设置硬盘缓存的最大值,单位为M
    private static int maxSizeForDiskLruCache=0;
    //设置内存缓存的最大值,单位为M
    private 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 block
        e.printStackTrace();
    }
      return false;
  }

  public void deleteDiskCache(){
      try {
          if(mDiskLruCache!=null){
              mDiskLruCache.delete();
          }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
  }

  public void removeDiskCache(String key){
      if(mDiskLruCache!=null){
          try {
            mDiskLruCache.remove(key);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
      }
  }

  public  void deleteFile(Context context,String dirName){
      try {
            DiskLruCache.deleteContents(packDiskCacheFile(context,dirName));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.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 block
            e.printStackTrace();
        }
      }
  }

}

public class LruCacheManager {

    private LruCache<String, Bitmap> lruCache;
    public LruCacheManager(){
        this((int)Runtime.getRuntime().maxMemory()/1024/8);
    }
    //设置自定义大小的LruCache
    public LruCacheManager(int maxSize){
        lruCache=new LruCache<String, Bitmap>(maxSize*1024){

            @Override
            protected 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 instead

        StringBuilder 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 Files
    public 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 off
        DiskLruCache 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 cache
        directory.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 leaks
        journalWriter.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 value
        if (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;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);
    }

    @Nullable
    @Override
    public 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() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemClickListener.onItemClick(null,0);
            }
        });
        album.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                onItemClickListener.onItemClick(null,1);
            }
        });
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public 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">
    <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/album"
            android:textSize="18dp"
            android:textColor="@color/white"
            android:gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
        <TextView
            android: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.RecyclerView
        android: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" />
    <RelativeLayout
        android:id="@id/container"
        android:background="#f3f3f3"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="50dp">
        <View
            android:background="@color/grey"
            android:layout_width="match_parent"
            android:layout_height="0.5dp"/>
        <Button
            android: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>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta content="IE=7.0000" http-equiv="X-UA-Compatible"> <title>jquery 访QQ相册</title> <meta content="text/html; charset=utf-8" http-equiv=content-type> <script type=text/javascript src="js/jquery.js"></script> <script type=text/javascript src="js/images.js"></script> <style type="text/css"> * {line-height: 150%} .image {text-align: center; line-height: 590px; margin: 30px auto 0px; width: 850px; height: 620px} .image img { overflow:hidden} img {border:0px} #photo_content {text-align: center} .container {margin: 20px 0 0 0; width: 960px} div#container {z-index: -2; background: #fff; border-top: #fff 1px solid} body {margen: 0px; padding: 0px; } ul {margin: 0px; padding:0px;} li {margin: 0px; padding:0px;} .wrap { text-align: left; margin: 0px auto} #wrap {padding-bottom: 10px; background-color: #fff; min-height: 450px; clear: both; } .wrap {width: 960px} .switch {margin: 0px auto; width: 708px; background: url(images/switch_link_11.jpg) no-repeat center 0px; height: 87px} .clear {clear: both} .icon1 {text-indent: -9999px;padding: 30px 0 0 18px; width: 15px; display: inline; float: left;} .icon1 a {width: 15px; display: block; background: url(images/sprite.gif) no-repeat -693px -92px; height: 30px} .icon1 a:hover {background: url(images/sprite.gif) no-repeat -693px -132px} .icon2 {text-indent: -9999px; width: 15px; float: right; padding:30px 15px 0 0;} .icon2 a {width: 15px; display: block; background: url(images/sprite.gif) no-repeat -670px -92px; height: 30px} .icon2 a:hover {background: url(images/sprite.gif) no-repeat -670px -132px} .switch_center {width: 585px; float: left; height: 83px; margin-left: 28px; overflow: hidden} .switch_center ul {} .switch_center li {width: 66px; display: inline; float: left; height: 66px; margin:0 20px 0 0; padding: 10px 0 0 0} .switch_center li a {border: #ccc 1px solid;width: 60px; display: block; height: 60px;} .switch_center li a img {width: 60px; height: 60px} .switch_center li a.on {border: #ff9900 1px solid} .switch_center li a:hover {border: #ff9900 1px solid;} .clear {clear: both} .loading {line-height:520px; width:850px; background: url(images/loading.gif) #333 no-repeat center center; height: 520px} </style> </head> <body> <div id="wrap" class="wrap"> <div id="page3"> <div id="photo_content"> <div class="container"> <div class="switch"> <div class="icon1"> <a onFocus="this.blur();" title="上一个" href="javascript:void(0);">上一个</a> </div> <div id="pics" class="switch_center"> <ul> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/1.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/2.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/3.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/4.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/5.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/6.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/7.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/8.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/9.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/10.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/11.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/12.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/13.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/14.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/15.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/16.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/17.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/18.jpg"></a></li> <li><a title="" href="javascript:void(0);"><img alt="" src="pics/19.jpg"></a></li> </ul> </div> <div class="icon2"><a onFocus="this.blur();" title="下一个" href="javascript:void(0);">下一个</a></div> <div class="clear"></div> </div> <div id="bigpics" class="image" title=""><IMG id=scollimg src="pics/1.jpg"></div> </div> </div> </div> </div> </body> </html>
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值