简单图库软件的实现(联网下载图片保存到sdcard在Listview中展示,并作为ContentProvider为其他软件提供图库数据)

先看效果图:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

这就是一个可以联网的图库软件,下面我们来看看需求


业务需求

1.判断是否第一次运行,第一次运行,提示添加新条目

2.点击添加按钮,弹出对话框,输入图片网址和标题

3.下载图片保存到本地SD卡中

4.数据库中保存图片文件路径和图片标题和URL地址

5.listview中列出已保存的所有条目,添加条目后,同步展现到listview中

6.选中listview中一个条目,点击删除,删除存储的条目,同步展现到listview

7.长按listview的条目,弹出删除菜单项,点击菜单项也可以删除条目

8.提供contentprovider供其他软件访问数据库


问题分析

虽然要求看起来挺多但是可以大致分为3部分去实现

  1. 数据的下载保存,包括存储到本地sdcard 和数据库中
  2. 数据的删除
  3. 为其他软件提供数据库接口

主要代码实现

首先用户输入图片地址我们应该去下载图片并保存到本地,此时下载图片属于耗时且需要联网的操作所以不能在ui线程中实现,我们创建异步任务AsyncTask完成图片下载,并通知UI线程更新进度条界面。下载完成应该返回图片的保存地址准备将数据写入数据库中 由于要展示下载的进度我们利用接口回调比较好实现。下面是下载图片的异步任务代码:

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import android.os.AsyncTask;
import android.os.Environment;

public class MyTask extends AsyncTask<String, Integer, String> {

    public interface CallBack {
        public void start(); //主界面展示一个进度条

        public void updataProgress(int progress); //更新进度条

        public void finish(String imgPath); //下载完成返回文件保存到绝对路径
    }

    CallBack cb;

    public MyTask(CallBack cb) {
        super();
        this.cb = cb;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        if (cb != null) {
            cb.start(); //准备工作
        }
    }

    @Override
    protected String doInBackground(String... params) {
        // 1.HttpURLConnection
        HttpURLConnection conn = null;
        String imgPath = null;
        // 2.URL
        try {
            URL url = new URL(params[0]);
            // 3.url.openConnection
            conn = (HttpURLConnection) url.openConnection();
            // 4.InputStream
            InputStream in = conn.getInputStream();
            // 获取该文件的总长度
            int total = conn.getContentLength();
            // 5.获取保存文件的路径及文件 名/sdcard/image
            String path_sdcard = Environment.getExternalStorageDirectory()
                    .getAbsolutePath() + "/image";
            File fileParent = new File(path_sdcard);
            // 判断该目录是否存在,如果不存在,创建该目录
            if (!fileParent.exists()) {
                // 创建目录
                fileParent.mkdirs();
            }
            String arr[] = params[0].split("/");
            String filenameString = arr[arr.length - 1];
            // 6.创建File对象,再拿到OutputStream
            File file = new File(path_sdcard, filenameString);
            if (file.exists()) {
                return file.getAbsolutePath();
            }
            // 用来返回该img路径
            imgPath = file.getAbsolutePath();
            OutputStream out = new FileOutputStream(file);

            byte[] buffer = new byte[4096];
            int sum = 0;
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
                // 累加
                sum = sum + len;
                // 计算百分比
                int per = (int) (sum * 100f / total);
                // 发布进度值
                publishProgress(per);
            }
            out.flush();
            out.close();
            in.close();
            // 返回当前被保存的img的绝对路径
            return imgPath;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        //下载异常返回NULL
        return null;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // TODO Auto-generated method stub
        super.onProgressUpdate(values);
        if (cb != null) {
            cb.updataProgress(values[0]); //更新进度条
        }
    }

    /*
     * result表示的是图片所在的的路径
     */
    @Override
    protected void onPostExecute(String result) {
        // TODO Auto-generated method stub
        super.onPostExecute(result);
        if (cb != null) {
            cb.finish(result); //返回图片地址
        }
    }

}

图片下载完成并且保存到本地了 此时我们应该将图片名称 URL 绝对路径 写入数据库。 那么此时我们应该开始创建数据库了,自定义MySqliteHelper 继承SQLiteOpenHelper 就好了,代码如下

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class MySqliteHelper extends SQLiteOpenHelper {
    public MySqliteHelper(Context context) {
        super(context, "picture.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创表
        String sql = "create table img(_id integer primary key autoincrement,name text ,url text,path text)";
        db.execSQL(sql);
        Log.d("onCreateDataBase", "helper onCreate create table img");


    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub

    }

}

数据库有着落了,那么我们应该想着如何将数据展示到listview中,这里ListView中的每个item包含了一张图片一个文本 。 这里就用就灵活的BaseAdapter完成,我们自定义一个MyAdapter继承自BaseAdapter。

import java.io.File;
import java.util.ArrayList;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MyAdapter extends BaseAdapter {
    ArrayList<Picture> data;
    Context context;
    LayoutInflater inflater;
    int progress;

    public MyAdapter(ArrayList<Picture> data, Context context) {
        super();
        this.data = data;
        this.context = context;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ViewHolder holder = null;

        if (convertView == null) {
            holder = new ViewHolder();
            convertView = inflater.inflate(R.layout.list_item, null);
            holder.tv_1 = (TextView) convertView.findViewById(R.id.tv);
            holder.iv = (ImageView) convertView.findViewById(R.id.img);
            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Picture picture = data.get(position);
        File file = new File(picture.path);
        if (file.exists()) { // 如果sd卡的图片存在去设置图片
            holder.tv_1.setText(picture.name);
            Bitmap bitmap = BitmapFactory.decodeFile(picture.path);
            holder.iv.setImageBitmap(bitmap);
        } else { // 图片不存在提示用户
            Toast.makeText(context, picture.name + "好像出了点问题,图片是否被你删除?",
                    Toast.LENGTH_SHORT).show();
            // 本地图片被删除 删除数据库中的数据
            MySqliteHelper helper = new MySqliteHelper(context);
            SQLiteDatabase db = helper.getWritableDatabase();
            String sql = "DELETE FROM img WHERE path = '" + picture.path + "'";
            db.execSQL(sql);

        }

        return convertView;
    }

    class ViewHolder {
        TextView tv_1;
        ImageView iv;

    }

}

数据展示问题也解决了,那么 接下来就是删除事件,点击listview 或者长按listview中的item都有对应的监听事件分别是setOnItemClickListener、 setOnItemLongClickListener
删除事件的实现:

lv.setOnItemLongClickListener(new OnItemLongClickListener() {

            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view,
                    final int position, long id) {
                AlertDialog.Builder builder = new AlertDialog.Builder(
                        MainActivity.this);

                builder.setTitle("刪除" + data.get(position).name).setMessage(
                        "此操作不可逆,是否继续?");
                // 相当于确定
                builder.setPositiveButton("确定",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {

                                /*
                                 * 注释部分为普通sql查询语句 String sql =
                                 * "delete from img where url='" +
                                 * data.get(position).url + "'";
                                 * db.execSQL(sql);
                                 */

                                // 1.获取连接服务地址
                                Uri uri = Uri
                                        .parse("content://com.picture.provider");
                                // 2.获取ContentResolver
                                ContentResolver cr = getContentResolver();
                                // 3.根据位置获取到数据库中相应的url 作为条件删除相应的数据
                                cr.delete(uri, "url='" + data.get(position).url
                                        + "'", null);

                                File file = new File(data.get(position).path);
                                if (file.exists()) { // 如果存在那么删除本地文件
                                    file.delete();
                                }
                                data = readDataBase(); // 读取数据库中的内容

                                MainActivity.this.position = -1;// 每次删除完毕设置当前位置为-1 为下一次点击做准备

                                adapter.notifyDataSetChanged();// 提示更新界面

                            }
                        });
                // 相当于取消 这里啥都不干
                builder.setNegativeButton("取消",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {

                            }
                        });

                builder.show();

                return false;
            }
        });

public void del(View view) {// 删除图片
        if (position != -1) {
            AlertDialog.Builder builder = new AlertDialog.Builder(
                    MainActivity.this);

            builder.setTitle("刪除" + data.get(position).name).setMessage(
                    "此操作不可逆,是否继续?");
            // 相当于确定
            builder.setPositiveButton("确定",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            /*
                             * String sql = "delete from img where url='" +
                             * data.get(position).url + "'"; db.execSQL(sql);
                             */
                            Uri uri = Uri
                                    .parse("content://com.picture.provider");
                            // 获取ContentResolver
                            ContentResolver cr = getContentResolver();
                            // 准备数据
                            cr.delete(uri, "url='" + data.get(position).url
                                    + "'", null);

                            File file = new File(data.get(position).path);
                            if (file.exists()) { // 如果存在那么删除本地文件
                                file.delete();
                            }
                            data = readDataBase(); // 读取数据库中的内容
                            if (data.size() == 0) { // 没有东西那么接下的链接可以直接下载
                                flag = true;
                                MainActivity.this.position = -1;
                            }
                            adapter.notifyDataSetChanged();// 提示更新界面
                        }
                    });
            // 相当于取消
            builder.setNegativeButton("取消",
                    new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    });

            builder.show();
        } else {
            Toast.makeText(this, "当前没有选中任何图片!", Toast.LENGTH_LONG).show();
        }

    }

删除功能也实现了,接下来我们实现ContentProvider功能其实也简单,写一个类继承自ContentProvider

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

public class MyContentProvider extends ContentProvider {
    MySqliteHelper helper;

    @Override
    public boolean onCreate() {
        helper = new MySqliteHelper(getContext());
        if (helper != null) {
            return true;
        }
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = helper.getWritableDatabase();

        Cursor cursor = db.query("img", projection, selection, selectionArgs,
                null, null, sortOrder);
        cursor.setNotificationUri(getContext().getContentResolver(), uri); // 通知界面更新
        return cursor;
    }

    @Override
    public String getType(Uri uri) {

        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.e("insert", " " + uri.getAuthority());
        SQLiteDatabase db = helper.getWritableDatabase();
        long id = -1;
        id = db.insert("img", null, values);
        db.close();
        getContext().getContentResolver().notifyChange(uri, null);
        return ContentUris.withAppendedId(uri, id); 
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.e("delete", "delete");
        SQLiteDatabase db = helper.getWritableDatabase();
        int count = db.delete("img", selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return count; //返回删除的条数
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        Log.e("update", "update"+selection);
        SQLiteDatabase db = helper.getWritableDatabase();
        int count = db.update("img", values, selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

}

在xml中注册provider提供给其他程序

   <provider
            android:name="com.wenxiangli.MyContentProvider"
            android:authorities="com.picture.provider"
            android:exported="true" >
    </provider>

至此我们需要的功能都实现了。接下来说几个遇到的问题注意点:

1.权限一定不要忘记了

 <!-- 联网权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- SDcard的读写权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!-- 读取Sdcard状态权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

2.为了完成第二次启动加载本地图片,我们应该先从数据库读出数据然后绑定到集合中去通知adapter更新
3.删除特别注意越界问题,这里我是通过每次删除结束设置positon为-1,当点击事件产生将改变position的值去判断是否删除。
4.保证图库中的数据唯一,所以每次下载前判断图片是否存在,存在就不去下载。直接提示是否更改文件的名字
这里写图片描述
这里写图片描述


下面附上完整源码地址需要的可以在这下载
链接:http://pan.baidu.com/s/1dEW5JBr 密码:m3jb

写这么长不容易啊,如果对你有帮助赏给回复 啊哈哈

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值