Picture Flash(图片放映)

来源:互联网 发布:js复合防水涂料哪种好 编辑:程序博客网 时间:2024/04/30 08:52

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/49300989和http://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的时候,根据所选的文件路径,查找该文件夹下的图片,并对数据进行刷新。

@Overridepublic 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
原创粉丝点击