Picture Flash(图片放映)

Picture Flash (图片放映)
考点: 数据库操作, 图片内存管理, 列表类视图, 动画, [异步线程]
功能描述:
1.创建放映专辑(专辑创建界面)
用户从手机中选定多张图片, 设置图片专辑名称后, 点击确认创建一个图片flash专辑.
1)图片列表从系统媒体库中查询获取, 按图片媒体库中的添加时间排列
2)采用两列多行的方式显示图片列表
3)用户可以输入Flash专辑的名称
4)选定多张图片并且输入专辑名称后, 点击OK按钮后创建一个图片Flash专辑存到数据库.

其他说明:
A.用户点击图片即可选定图片, 如已选中则转为未选定
B.若用户没有输入专辑名, 点击OK按钮时则Toast提示”请输入专辑名”
C.若用户未选择任何图片, 点击OK按钮时则Toast提示”请选择图片”
D.图片的加载会比较耗时, 考虑使作异步线程进行加载

这里写图片描述

图2.1

2.专辑列表浏览(主界面)
1)以列表的方式显示用户之前创建的所有专辑
2)列表每项显示专辑名, 及其中的第一张图片作为封面
3)点击列表中的专辑即跳转到图片Flash播放界面(见功能点3)
4)点击标题栏右边的”+”按钮跳转到专辑创建界面

其他说明:
A.如初始进入程序专辑列表为空, 可在列表空白处给出提示信息
B.如列表较多时加载数据需要一些时间, 考虑使用异步线程加载并显示一个loading

这里写图片描述

图 2.2

3.播放图片Flash专辑(图片Flash播放界面)
1)进入界面后即开始播放当前专辑的所有图片
2)以图片由大到小的镜头拉近动画效果(参看天猫主页的顶部Banner效果)顺序播放图片, 循环播放
3)在页面底部以 “ 当前页 / 总页数”的格式显示播放进度
4)切换到下一张图片的时机为当图片缩放至布满屏幕时(高度或宽度布满即可, 保持图片原比例), 图片起始的大小为布满大小的1.2倍
在播放其间点击屏幕任意位置即退出播放界面

这里写图片描述
图2.3

这是来到公司的第二个小项目,做一个图片专辑放映。最终实现的效果如下所示:
这里写图片描述

然后,附上程序的流程图
这里写图片描述

接着,附上项目的MUL图。
这里写图片描述

这里写图片描述

分析一下需求,要实现上述效果,需要做的有以下几点:
1. 自定义imagerloader类的实现
2. 图片路径的获取
3. 数据库的操作
4. 图片的放映
5. recylerview的使用以及图片大小的适配

要完成上述功能,首先我们必须写好一个imageloader类,一是不能动不动就OOM,二是使用起来要方便简洁。刚好想起鸿洋有一篇讲解仿微信相册的博客,于是就跑去看了。

主要是http://blog.csdn.net/lmj623565791/article/details/49300989http://blog.csdn.net/lmj623565791/article/details/39943731

首先是imageloader类,获取一个单例,使用一个线程进行加载,队列的调度方式为先进后出

public static ImageLoader getInstance()
{

    if (mInstance == null)
    {
        synchronized (ImageLoader.class)
        {
            if (mInstance == null)
            {
                mInstance = new ImageLoader(1, Type.LIFO);
            }
        }
    }
    return mInstance;
}

接着是初始化工作,首先创建一个线程,不断轮训,让线程池不断查找是否有任务。

private void init(int threadCount, Type type)
    {
        // loop thread
        mPoolThread = new Thread()
        {
            @Override
            public void run()
            {
                Looper.prepare();

                mPoolThreadHander = new Handler()
                {
                    @Override
                    public void handleMessage(Message msg)
                    {
                        mThreadPool.execute(getTask());
                        try
                        {
                            mPoolSemaphore.acquire();
                        } catch (InterruptedException e)
                        {
                        }
                    }
                };
                // 释放一个信号量
                mSemaphore.release();
                Looper.loop();
            }
        };
        mPoolThread.start();

        // 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        mLruCache = new LruCache<String, Bitmap>(cacheSize)
        {
            @Override
            protected int sizeOf(String key, Bitmap value)
            {
                return value.getRowBytes() * value.getHeight();
            };
        };

        mThreadPool = Executors.newFixedThreadPool(threadCount);
        mPoolSemaphore = new Semaphore(threadCount);
        mTasks = new LinkedList<Runnable>();
        mType = type == null ? Type.LIFO : type;

    }

接着,便是加载图片。首先通过LruCache算法,根据图片路径从内存取出bitmap,如果为空,创建线程并将线程作为一个Task添加至线程池中。

public void loadImage(final String path, final ImageView imageView)
    {
        // set tag
        imageView.setTag(path);
        // UI线程
        if (mHandler == null)
        {
            mHandler = new Handler()
            {
                @Override
                public void handleMessage(Message msg)
                {
                    ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
                    ImageView imageView = holder.imageView;
                    Bitmap bm = holder.bitmap;
                    String path = holder.path;
                    if (imageView.getTag().toString().equals(path))
                    {
                        imageView.setImageBitmap(bm);
                    }
                }
            };
        }

        Bitmap bm = getBitmapFromLruCache(path);
        if (bm != null)
        {
            ImgBeanHolder holder = new ImgBeanHolder();
            holder.bitmap = bm;
            holder.imageView = imageView;
            holder.path = path;
            Message message = Message.obtain();
            message.obj = holder;
            mHandler.sendMessage(message);
        } else
        {
            addTask(new Runnable()
            {
                @Override
                public void run()
                {

                    ImageSize imageSize = getImageViewWidth(imageView);

                    int reqWidth = imageSize.width;
                    int reqHeight = imageSize.height;

                    Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth,
                            reqHeight);
                    addBitmapToLruCache(path, bm);
                    ImgBeanHolder holder = new ImgBeanHolder();
                    holder.bitmap = getBitmapFromLruCache(path);
                    holder.imageView = imageView;
                    holder.path = path;
                    Message message = Message.obtain();
                    message.obj = holder;
                    // Log.e("TAG", "mHandler.sendMessage(message);");
                    mHandler.sendMessage(message);
                    mPoolSemaphore.release();
                }
            });
        }

    }

接下来,根据图片的原有宽高,缩放成指定大小的图片。故须计算一下inSampleSize。

private int calculateInSampleSize(BitmapFactory.Options options,
                                      int reqWidth, int reqHeight)
    {
        // 源图片的宽度
        int width = options.outWidth;
        int height = options.outHeight;
        int inSampleSize = 1;

        if (width > reqWidth && height > reqHeight)
        {
            // 计算出实际宽度和目标宽度的比率
            int widthRatio = Math.round((float) width / (float) reqWidth);
            int heightRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = Math.max(widthRatio, heightRatio);
        }
        return inSampleSize;
    }

然后根据上述的inSampleSize,对图片进行缩放处理

private Bitmap decodeSampledBitmapFromResource(String pathName,int reqWidth, int reqHeight)
    {
        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(pathName, options);
        // 调用上面定义的方法计算inSampleSize值
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFile(pathName, options);

        return bitmap;
    }

到这里,一个基本的ImageLoader已经基本完成了。
然后,我们来到图片页面。通过Content Provider对数据进行查找,获取所有图片的路径、张数和popwindow的路片路径和首张图片的路径。

private class ScanAsyncTask extends          AsyncTask<Void,Void,Void>{

        @Override
        protected void onPreExecute() {
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)){
                view.showToast("暂无外部存储");
                return;
            }
            view.showLoding();
        }

        @Override
        protected Void doInBackground(Void... voids) {

            String firstImage = null;

            Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            ContentResolver mContentResolver = mContext.getContentResolver();

            // 只查询jpeg和png的图片
            Cursor mCursor = mContentResolver.query(mImageUri, null,
                    MediaStore.Images.Media.MIME_TYPE + "=? or "
                            + MediaStore.Images.Media.MIME_TYPE + "=?",
                    new String[] { "image/jpeg", "image/png" },
                    MediaStore.Images.Media.DATE_ADDED+" DESC");

            while (mCursor.moveToNext()){
                //获取图片路径
                String path = mCursor.getString(mCursor.getColumnIndex(
                        MediaStore.Images.Media.DATA
                ));

                if (firstImage == null){
                    firstImage = path;
                }

                File parentFile = new File(path).getParentFile();
                if (parentFile == null)
                    continue;

                String dirPath = parentFile.getAbsolutePath();
                ImageFloder imageFloder = null;

                // 利用一个HashSet防止多次扫描同一个文件夹(不加这个判断,图片多起来还是相当恐怖的~~)
                if (mDirPaths.contains(dirPath))
                {
                    continue;
                }else {
                    mDirPaths.add(dirPath);
                    imageFloder = new ImageFloder();
                    imageFloder.setDir(dirPath);
                    imageFloder.setFirstImagePath(path);
                }

                int picSize = parentFile.list(new FilenameFilter() {
                    @Override
                    public boolean accept(File file, String filename) {
                        return filename.endsWith(".jpg")
                                || filename.endsWith(".png")
                                || filename.endsWith(".jpeg");
                    }
                }).length;
                totalCount += picSize;

                imageFloder.setCount(picSize);
                mImageFloders.add(imageFloder);

                if (picSize > mPicsSize){
                    mPicsSize = picSize;
                    mImgDir = parentFile;
                }

            }
            mCursor.close();
            mDirPaths = null;
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            view.dismissLoading();

            if (mImgDir == null){
                view.showToast("一张图片都没扫描到。。");
                return;
            }

            mImgs = Arrays.asList(mImgDir.list());
            view.setPictureData(mImgs,mImgDir.getAbsolutePath());
            view.setPicTotalCount(totalCount+"张");
            view.setImageFloders(mImageFloders);
        }
    }

接着,在PictureActivity通过recyclerview对数据进行展示,点击popwindow的时候,根据所选的文件路径,查找该文件夹下的图片,并对数据进行刷新。

@Override
public void setImageFloders(List<ImageFloder> list) {

        mImageFloders = list;
        mListImageDirPopupWindow = new ListImageDirPopupWindow(
                ViewGroup.LayoutParams.MATCH_PARENT, (int) (App.sScreenHeight * 0.7),
                mImageFloders, LayoutInflater.from(getApplicationContext())
                .inflate(R.layout.list_dir, null));

        mListImageDirPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                // 设置背景颜色变暗
                WindowManager.LayoutParams lp = getWindow().getAttributes();
                lp.alpha = 1.0f;
                getWindow().setAttributes(lp);
            }
        });
        // 设置选择文件夹的回调
        mListImageDirPopupWindow.setOnImageDirSelected(this);
    }

    @Override
    public void selected(ImageFloder floder) {
        mImgDir = new File(floder.getDir());
        mImgs = Arrays.asList(mImgDir.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String filename) {
                if (filename.endsWith(".jpg") || filename.endsWith(".png")
                        || filename.endsWith(".jpeg"))
                    return true;
                return false;
            }
        }));

        mAdapter.setData(mImgs, mImgDir);
        idTotalCount.setText(mImgs.size() + "张");
        idChooseDir.setText(floder.getName());
        mListImageDirPopupWindow.dismiss();
    }

当点击右上角的时候,创建一个弹窗,用于添加所选图片的专辑名字。创建成功,则把选过的图片取消,把edittext清空,并将所选图片的路径和专辑名称存入数据库。

mBuilder = new AlertDialog.Builder(this);
        mBuilder.setTitle("创建专辑")
                .setView(dialog)
                .setPositiveButton("确定",new DialogInterface.OnClickListener() {
                  @Override
                   public void onClick(DialogInterface dialogInterface, int i) {
                       if (!editText.getText().toString().equals("")){
                           mDialog.cancel();
                           mPresenter.createAlumb(editText.getText().toString(),mAdapter.getmSelectedImage());
                           editText.setText("");
                           mAdapter.clearSelected();
                           Toast.makeText(mContext,"创建成功",Toast.LENGTH_SHORT).show();
                       }else {
                           mDialog.show();
                           Toast.makeText(mContext,"专辑名不能为空",Toast.LENGTH_SHORT).show();
                      }
                  }
                 }).setNeutralButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                editText.setText("");
            }
        });
        mDialog = mBuilder.create();

那么,创建成功的时候,要将数据存入数据库,那么,我们得写一个SqlHelper类,用于管理数据的增删改查。

public class DBOpenHelper extends SQLiteOpenHelper {
    //创建一个picture表,有id(自增长)、图片路径、专辑名称三个属性
    private static final String CREATE_TABLE_SQL = "create table  picture( id " + "integer primary key autoincrement,path varchar not null, " +"alumb varchar not null)";

    public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_TABLE_SQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

接着就是一个service类,用于实现数据库的增删改查

public class PictureService {

    DBOpenHelper mDbHelper;
    SQLiteDatabase mDb;

    public PictureService(Context context, int version) {
        mDbHelper = new DBOpenHelper(context, "picture.db", null, version);
    }

    public void insert(String path,String alumb) {
        mDb = mDbHelper.getWritableDatabase();
        mDb.execSQL("insert into picture(path,alumb) values(?,?)",
                new Object[]{path, alumb});
    }

    public void delete(String alumb) {
        mDb = mDbHelper.getWritableDatabase();
        mDb.execSQL("delete from picture where alumb=?", new String[]{alumb});
    }

    public List<AlumbBean> getAlumbInfo() {

        mDb = mDbHelper.getWritableDatabase();
        List<AlumbBean> alumbList = new ArrayList<>();

        Cursor cursor = mDb.rawQuery("select * from picture",null);
        boolean first = true;
        String myPath = "";
        String myAlumb = "";

        if (cursor.moveToFirst()){
            do {

                String path = cursor.getString(cursor.getColumnIndex("path"));
                String alumb = cursor.getString(cursor.getColumnIndex("alumb"));
                ImageBean bean = new ImageBean();
                bean.setPath(path)
                        .setAlumb(alumb);

                    AlumbBean alumbBean = new AlumbBean(myPath,myAlumb);
                    if (alumbBean.getAlumb().equals(alumb)){
                        myPath = myPath + "," +path;
                    }else {
                        if (first){
                            first = false;
                        }else {
                            alumbList.add(alumbBean);
                        }
                        myPath = path;
                        myAlumb = alumb;

                    }
                }while (cursor.moveToNext());
                alumbList.add(new AlumbBean(myPath,myAlumb));
            }
            cursor.close();

        return alumbList;
    }
}

到这里,我们已经可以实现专辑的创建以及图片的选择了。那么剩下的,就是主页面的展示和专辑详情页面的展示。在主页面,我们通过PictureService里的getAlumbInfo(),我们可以获取到专辑列表,我们只需配合recyclerview将数据展示出来即可。

点击主页面专辑的时候,回跳转至ImagePagerActivity
这里是一个viewpager嵌套着fragment,循环滑动的页面。
fragment通过获取到到url,对图片进行加载,配合scale动画。

由于viewpager会自动缓存左右两个页面,即使设置了setOffscreenPageLimit(0);也无效,查看源码,发现limit小于1的时候,会自动设置为1,故设置不缓存是不可行的。所以使用懒加载的方式,当页面可见的时候再加载动画,就不会出现页面跳转之后没有缩放动画了

public class ImageDetailFragment extends Fragment {

    private String ImageUrl;
    private ImageView img;
    private Animation animation;

    public static ImageDetailFragment newInstance(String imageUrl) {
        final ImageDetailFragment f = new ImageDetailFragment();

        final Bundle args = new Bundle();
        args.putString("url", imageUrl);
        f.setArguments(args);

        return f;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ImageUrl = getArguments()!=null?getArguments().getString("url"):null;
        getArguments().remove("url");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.image_detail_fragment,container,false);
        img = (ImageView) v.findViewById(R.id.image);
        ImageLoader.getInstance(3, ImageLoader.Type.LIFO)
                .loadImage(ImageUrl,img);
        animation = AnimationUtils.loadAnimation(getContext(),R.anim.scale_in);

        img.setClickable(true);
        img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getActivity().finish();
            }
        });
        return v;
    }

    @Override
    public void onResume() {
        super.onResume();
        animation = AnimationUtils.loadAnimation(getContext(),R.anim.scale_in);
        img.startAnimation(animation);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if (isVisibleToUser&&animation!=null){
            img.startAnimation(animation);
        }
        super.setUserVisibleHint(isVisibleToUser);
    }
}

到这里,项目的分析基本就结束了。最后附上代码链接:
https://github.com/RuijiePan/PictureFlash.git

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值