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
- Picture Flash(图片放映)
- Picture Control 打开图片
- 鸿森超级酷图片放映器 免费
- 精确缩放图片 Resize Picture
- mfc- Picture control显示图片
- MFC Picture Control显示图片
- MFC--图片控件Picture Control
- 图片自适应Picture控件大小
- MFC picture 控件加载图片
- MFC图片控件Picture Control
- mfc图片自适应picture control
- WTL Picture Control显示图片
- picture控件动态加载图片
- 幻灯片放映
- #童游大放映
- picture
- picture
- picture
- iRedMail退信问题的解决
- java 中的IO对于目录创建应当注意到问题
- POJ1182食物链 并查集 (向量偏移 讲解)
- 第十五周5
- Windows下开机自启动Tomcat7
- Picture Flash(图片放映)
- Tomcat的HTTP与AJP协议
- poj1637 Sightseeing tour
- Error while obtaining UI hierarchy XML file:com.android.ddmlib.SyncException:
- SSH网站实录(7)新闻模块
- unity连接sqlserver数据库发布后不能连接
- windows安装pip工具
- 以太坊 链私有链环境搭建(windows)
- CocoaPods多版本协同工作