zoukankan      html  css  js  c++  java
  • Android项目实战之(1)-- 开发一个"快速冲浪"的程序

    概述:这个小程序,你讲学习到基本控件(Button,Listview,Gridview,TextView等)的使用技巧,AssetManager类的使用,XML数据的解析方式,BaseAdapter,几种布局的使用,Sqlite的使用等等。

     一、简单需求设计

        左右分栏的形式,左栏为一级菜单,右栏为二级菜单。通过点击一级菜单列表在右栏现实二级菜单,在二级菜单中点击相关选项进入网页。一级菜单,二级菜单均有编辑功能,或增,或删,或改。为提高用户体验,将二级菜单的显示模式定为两种,一为列表模式,二为表格方式。将在一二级菜单中添加列表头或者列表尾进行添加操作。为更迎合用户的偏好使用,将设计将用户本次的设置保存到系统中,方便用户下次使用。提供恢复默认设置的选项,即将数据初始为原始状态。

    二、技术实现可能性预见

        就我本人而言,做过的项目不多,那么首先我会做一个简单的技术考察,或做一些小Demo。那么这里,我就以我的方式来阐述下这个项目需要用到的一个技术。首先,必然要用到xml的解析,那么xml有三种解析方式(这里只做简单说明):(1)DOM解析,Dom解析器会在解析xml文档是,把文档中所有的元素按照其出现的层次关系解析成一个个Node节点对象,Node节点对象提供了相应的方法去获取父节点和子节点,那么我们可以通过这些方法读取整个xml并对其操作,它的缺点是一次性加载整个xml文件,那么对于移动设备,内存是非常宝贵的,所以我们不用它。(2)SAX解析,SAX解析允许在读取文档的时候,即对文档进行处理,而不必等到整个文档撞在完毕才对其进行操作。它是基于事件驱动的(3)PULL方式,和SAX一样也是基于事件驱动的,使用起来也比较简单,那么我们就用Pull。Activity之间的数据传递和保存,这个是完全可以处理的。调用相应的方法,或者将需要存储的数据保存到sqlite就可以了。这些开始开代码吧。功能的实现,基本没有什么问题了,那么下一步我们就来布局下界面。

    三、界面布局的设计

        根据之前的需求设计,大致将界面布局定为以下样式。

    四、项目关键技术点讲解及代码展示

    1、在Eclipse中新建工程,SurfingDemo,程序包名:org.winplus.sufing

    2、按照我的编码习惯,我喜欢先将UI弄好,然后直接取数据,并将其显示。那么根据上图UI的设计草图,我们可以将几个主要的布局进行排版设计。代码如下:

    surfing.xml(布局描述文件)

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?> 

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 

        android:layout_width="fill_parent" 

        android:layout_height="fill_parent" 

        android:orientation="horizontal" > 

        <!-- 左边的操作布局即,一级菜单的显示--> 

        <LinearLayout 

            android:id="@+id/layout_left" 

            android:layout_width="200px" 

            android:layout_height="wrap_content" 

            android:orientation="vertical" > 

            <TextView 

                android:id="@+id/left_msg" 

                android:layout_width="wrap_content" 

                android:layout_height="40px" 

                android:text="@string/txt_left_msg" /> 

            <ListView 

                android:id="@+id/list_first_classify" 

                android:layout_width="wrap_content" 

                android:layout_height="wrap_content" /> 

        </LinearLayout> 

        <!-- 右边布局,即二级菜单的显示--> 

        <LinearLayout 

            android:id="@+id/layout_right" 

            android:layout_width="wrap_content" 

            android:layout_height="wrap_content" 

            android:layout_weight="1" 

            android:orientation="vertical" > 

            <!-- 右边操作布局,显示模式的设置--> 

            <LinearLayout 

                android:id="@+id/layout_right_oper" 

                android:layout_width="fill_parent" 

                android:layout_height="40px" 

                android:orientation="horizontal" > 

                <TextView 

                    android:id="@+id/right_msg" 

                    android:layout_width="wrap_content" 

                    android:layout_height="wrap_content" 

                    android:layout_weight="1" 

                    android:text="@string/txt_right_msg" /> 

                <ImageButton 

                    android:id="@+id/display_list" 

                    android:layout_width="wrap_content" 

                    android:layout_height="wrap_content" 

                    android:background="#00000000" 

                    android:src="@drawable/ic_display_list"/> 

                <ImageButton 

                    android:id="@+id/display_grid" 

                    android:layout_width="wrap_content" 

                    android:layout_height="wrap_content" 

                    android:background="#00000000" 

                    android:src="@drawable/ic_display_grid"/> 

            </LinearLayout> 

            <!-- 二级菜单的主显示--> 

            <FrameLayout 

                android:layout_width="wrap_content" 

                android:layout_height="wrap_content" 

                android:layout_weight="1" > 

                <ListView 

                    android:id="@+id/second_classify_list" 

                    android:layout_width="wrap_content" 

                    android:layout_height="wrap_content" /> 

                     

                <GridView 

                    android:id="@+id/second_classify_grid" 

                    android:layout_width="wrap_content" 

                    android:layout_height="wrap_content" 

                    android:numColumns="auto_fit" 

                    android:verticalSpacing="5dp"   

                    android:horizontalSpacing="5dp" 

                    android:columnWidth="80dp" 

                    android:stretchMode="columnWidth"   

                    android:gravity="center"/> 

            </FrameLayout> 

        </LinearLayout> 

    </LinearLayout> 

    </span></span> 

        界面布局完成了,下一步我们先将一些数据整理出来,我把数据整理成xml文档,供程序初始化使用,数据文档如下:

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?> 

    <classify> 

    <!--视频(奇异高清,QQ视频,优酷,土豆,新浪视频)--> 

        <subclassify name="视频" icon="img/video.png"> 

            <url_data id="1" name="奇异高清" icon="img/vqiyi.png" url="http://www.qiyi.com" describe=""/> 

            <url_data id="1" name="QQ视频" icon="img/vqq.png" url="http://www.v.qq.com" describe=""/> 

            <url_data id="1" name="优酷" icon="img/vyouku.png" url="http://www.youku.com" describe=""/> 

            <url_data id="1" name="土豆" icon="img/vtudou.png" url="http://www.tudou.com" describe=""/> 

            <url_data id="1" name="新浪视频" icon="img/vsina.png" url="http://video.sina.com.cn/" describe=""/> 

        </subclassify> 

    <!-- 新闻(新浪新闻,新华网,腾讯新闻,网易新闻,中国新闻网) -->  

        <subclassify name ="新闻" icon="img/news.png"> 

            <url_data id="2" name="新浪新闻" icon="img/nsina.png" url="http://news.sina.com.cn/" describe=""/> 

            <url_data id="2" name="新华网" icon="img/nxinhua.png" url="http://www.xinhuanet.com/" describe=""/> 

            <url_data id="2" name="腾讯新闻" icon="img/nqq.png" url="http://news.qq.com/" describe=""/> 

            <url_data id="2" name="网易新闻" icon="img/n163.png" url="http://news.163.com/" describe=""/> 

            <url_data id="2" name="中国新闻网" icon="img/nchina.png" url="http://www.chinanews.com/" describe=""/> 

        </subclassify> 

    <!-- 军事(中华军事,铁血网,环球军事,新浪军事,东方军事)--> 

        <subclassify name ="军事" icon="img/war.png"> 

            <url_data id="3" name="中华军事" icon="img/wchina.png" url="http://military.china.com/zh_cn/" describe=""/> 

            <url_data id="3" name="铁血网" icon="img/wtiexue.png" url="http://www.tiexue.net/" describe=""/> 

            <url_data id="3" name="环球军事" icon="img/whuanqiu.png" url="http://mil.huanqiu.com/" describe=""/> 

            <url_data id="3" name="新浪军事" icon="img/wsina.png" url="http://mil.news.sina.com.cn/" describe=""/> 

            <url_data id="3" name="东方军事" icon="img/weast.png" url="http://mil.eastday.com/" describe=""/> 

        </subclassify> 

    </classify> 

    </span></span> 

    当然还有图片资源,资源图片我就不提供了。对此项目感兴趣的就自己PS或者直接到网站去截图吧。

    根据上面的数据显示,我定义了两个bean,来保存数据。

    UrlClassify.java(用于保存subclassify的Name和Icon)

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">package org.winplus.sufing; 

    public class UrlClassify { 

        private int id; 

        private String name; 

        private byte[] icon; 

        //getter setter method 

    </span></span> 

    UrlInfo.java(用于保存url_data标签的相关属性)

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">public class UrlInfo { 

        private int fid; 

        private String name; 

        private byte[] icon; 

        private String url; 

        private String describe; 

    //getter setter method 

    </span></span> 

        OK,实体Bean定义好了,现在可以解析xml数据了。上面的xml数据被我定义为urldata.xml并将其放在Android工程目录assets下,通过AssetManager管理。要解析xml数据,就要用到上面提到的3中方法,这里我们使用Pull(你可以使用以上任意一种处理方式)。为了程序更有扩展性,我们将处理xml数据的方法写成一个接口,然后再通过实现接口去真正解析xml数据。直接看代码。

    接口类很简单,就定义了一个接口:

    IxmlParseService.java(解析XMl数据的接口类)

    <span style="font-family: 'Microsoft YaHei'; font-weight: normal; "><span style="font-size:16px;">public interface IXmlParseService { 

        public Map<UrlClassify,ArrayList<UrlInfo>> getUrlsByParseXml(InputStream is) throws Exception;   

    PullParseService.java(实现解析数据的接口类),以下是主要方法 

    public Map<UrlClassify,ArrayList<UrlInfo>> getUrlsByParseXml(InputStream is) 

                throws Exception { 

            Map<UrlClassify,ArrayList<UrlInfo>> map = null; // 定义一个Map用于保存一级菜单和二级菜单数据(根据xml的结构,将一级菜单UrlClassify作为键值保存在Map中,UrlInfo数字列表保存为Map的值) 

            ArrayList<UrlInfo> classifies = null; // 定义保存UrlInfo的数组列表。 

            UrlClassify urlClassify = null; 

            UrlInfo urlInfo = null; 

             

            XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); // 得到一个PullParserFactory实例,用于创建Pull解析器 

            XmlPullParser parser = factory.newPullParser(); // 创建解析器 

            parser.setInput(is,"UTF-8"); // 设置解析格式 

            int type = parser.getEventType(); // 得到当前事件的类型(文档开始、结束,标签开始结束)  

            byte[] by = null; // 图片文件 

            while (type!=XmlPullParser.END_DOCUMENT) { // 当前事件类型为文档结束不再循环 

                String typeName = parser.getName(); 

                if (type==XmlPullParser.START_TAG) { 

                    if ("classify".equals(typeName)) { 

                        map = new LinkedHashMap<UrlClassify, ArrayList<UrlInfo>>(); 

                    }else if ("subclassify".equals(typeName)) { 

                        by = imgFile2Byte(parser.getAttributeValue(1)); 

                        urlClassify = new UrlClassify(parser.getAttributeValue(0), by); 

                        classifies = new ArrayList<UrlInfo>(); 

                    }else if("url_data".equals(typeName)){ 

                        by = imgFile2Byte(parser.getAttributeValue(2)); 

                         

                        int fid = Integer.valueOf( parser.getAttributeValue(0)); 

                        String name = parser.getAttributeValue(1); 

                        String url = parser.getAttributeValue(3); 

                        String describe = parser.getAttributeValue(4); 

                         

                        urlInfo = new UrlInfo(fid,name,by,url,describe); 

                        classifies.add(urlInfo); 

                    } 

                }else if (type == XmlPullParser.END_TAG) { 

                    map.put(urlClassify, classifies); 

                } 

                type = parser.next();   

            } 

            return map; 

        } 

    </span></span> 

        解析xml的方法搞定,解析出来的数据是显示在listview中的,我们通过listview.setAdapter()方法设置数据,于是就需要先定义一个继承Adapter的类。在定义Adapter类之前,我们又有必要设计好在listview显示的布局描述文件(当然你也可以使用系统自带的布局)。在这个项目中,我并不打算花太多的精力去做界面上的美化工作,只实现基本的功能,或者演示基本的使用情况。我将一级菜单数据显示的UI定义成如下的样式。

    根据上面的显示,定义了如下文件first_classify_adapter.xml(一级菜单Adapter布局文件)

    <span style="font-family: 'Microsoft YaHei'; font-weight: normal; "><span style="font-size:16px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 

        android:layout_width="fill_parent" 

        android:layout_height="fill_parent" 

        android:orientation="horizontal" > 

        <ImageView 

            android:id="@+id/edit" 

            android:layout_width="wrap_content" 

            android:layout_height="wrap_content" 

            android:visibility="gone" 

            android:src="@drawable/ic_edit" /> 

        <ImageView 

            android:id="@+id/img" 

            android:layout_width="80dip" 

            android:layout_height="40dip" /> 

        <TextView 

            android:id="@+id/text" 

            android:layout_width="wrap_content" 

            android:layout_height="wrap_content" 

            android:layout_weight="1" /> 

    </LinearLayout> 

    </span></span> 

        细心的读者会注意到,上面的布局文件中有一个隐藏的“编辑按钮”图标,非编辑情况下这个图标时隐藏不显示的,只有在编辑模式下才将图标显示。这个编辑功能应该是这样,在编辑模式下,点击编辑图片按钮,然后跳转到相应的Activity中,编辑当前项,然后将编辑过的项更新到数据库或者文件中,然后再返回当前的界面。为了减少对数据库的操作,我并没有打算更新数据后,再查一次数据库中的数据,然后在显示在界面。而且请注意:这个描述文件是在Adapter中使用的,我们都知道诸如startActivityForResult,startActivity(intent)是Activity中的方法,但在Adapter中怎么使用呢?没办法,我只有将SurfingDemoActivity通过构造函数传递到Adapter中,来实现引用,下面是关键代码。

    FirstClassifyAdapter.java(一级列表的Adapter)

    <span style="font-size:16px;">/**

        * 以下的关于显示的处理方式是googleIO大会提出的优化方式之一,推荐使用这种方式!

        */ 

       @Override 

       public View getView(int position, View convertView, ViewGroupparent) { 

          ViewHolder holder = null; 

          if (convertView == null) { 

            holder = new ViewHolder(); 

            convertView = inflater.inflate(R.layout.first_classify_adapter, null); 

            holder.img = (ImageView) convertView.findViewById(R.id.img); 

            holder.text = (TextView) convertView.findViewById(R.id.text); 

            holder.imgEdit = (ImageView) convertView.findViewById(R.id.edit); 

            convertView.setTag(holder); // 这点很关键,当初我就是忘记了设置Tag,导致整个列表值缓存了一屏数据,当数据超过一屏数据时就重复显示了。在网上也看到很多哥们关于重复显示的迷惑。 

          } else { 

            holder = (ViewHolder) convertView.getTag(); 

          } 

          final FirstClassify classify = mClassifies.get(position); 

          byte[] by = classify.getIcon(); 

          if (by != null) { 

            ByteArrayInputStream bais = new ByteArrayInputStream(classify.getIcon()); 

            holder.img.setImageDrawable(Drawable.createFromStream(bais,String.valueOf(position))); 

          } else { 

            holder.img.setBackgroundResource(R.drawable.ic_launcher); 

          } 

          holder.text.setText(classify.getName()); 

          if (isEdit) { 

            holder.imgEdit.setVisibility(View.VISIBLE); 

            holder.imgEdit.setOnClickListener(new OnClickListener() { 

      

               @Override 

               public void onClick(View v) { 

                  Log.i(TAG, "classify.getName=" + classify.getName()); 

                  Intent intent = new Intent(); 

               intent.setClassName("org.winplus.sufing","org.winplus.sufing.FirstClassifyEditActivity"); 

                  Bundle bundle = new Bundle(); 

                  bundle.putSerializable("classify", classify); 

                  intent.putExtras(bundle); 

      

                  mActivity.startActivityForResult(intent, 

                        Constant.REQUEST_EDIT_CLASSIFY); 

               } 

            }); 

          } else { 

            holder.imgEdit.setVisibility(View.GONE); 

          } 

          return convertView; 

       } 

      

       class ViewHolder { 

          public ImageView img; 

          public TextView text; 

          public ImageView imgEdit; 

    }</span> 

        当然还有二级分类的显示Adapter,二级分类显示又有两种(ListView,GridView)显示方式,这里就省略了,实现方式和上面FirstClassifyAdapter类一样。

        完成了上面的工作之后,我们终于可以将数据显示到界面上了!在自动生成的类SurfingDemoActivity中,先定义并初始化xml文件中相关的各个组件。从上面的需求以及surfing.xml描述文件中我们看到了,这个类中需要实现几个必须得事件监听类。当然也可以不通过实现的方式,直接重写控件的方法,这样如果控件多,实现起来就比较冗余,个人习惯吧。这个类中我们通过实现OnClickListener,OnItemClickListener,OnItemLongClickListener三个类来处理ListView的点击、长按,以及按钮的点击事件。

    这个类中,加载数据的流程是这样,先判断是否是第一次运行,如果是第一次运行则解析xml数据,并将其保存到数据库,然后再显示在界面。如果非第一次运行,则直接查询数据库中的数据,然后显示。SurfingDemoActivity.java这个类中关键代码如下。

    SurfingDemoActivity.java(代码中都有注释,这里就不说了。)

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">/**

        * 初始化数据(判断程序是否是第一次运行,如果是第一次运行则将数据加载到数据库中),关于是否第一次运行的判断,我是直接通过SharedPreferences,将数据保存在共享文件中,如果读取到的值为false表示第一次运行。第一次运行后设置共享文件中的值为true,即可实现。这个好像没有什么好的实现方式吧?

        */ 

       private void init() { 

          if (!utils.isFirstRunning()) { 

            map = new LinkedHashMap<FirstClassify, ArrayList<UrlInfo>>(); 

            // TODO: 加线程 

            initXML(); 

            initData(); 

            utils.setFirstRuslt(); 

          } 

          setDefaultData(false); 

          getSecondDisplay(); 

       } 

      

       /**

        * 解析数据

        */ 

       private void initXML() { 

          InputStream stream = null; 

          AssetManager manager = getAssets(); 

          try { 

            stream = manager.open("urldata.xml"); 

          } catch (IOException e) { 

            Log.i(TAG, e.getMessage()); 

          } 

          try { 

            map = new PullParseService(manager).getUrlsByParseXml(stream); 

          } catch (Exception e) {  

            e.printStackTrace(); 

          } 

       } 

       

       /**

        * 将一级分类和二级分类的数据加载到数据库中

        */ 

       private void initData() { 

          ArrayList<UrlInfo> urlInfos = new ArrayList<UrlInfo>(); 

          Iterator iter = map.entrySet().iterator(); 

      

          while (iter.hasNext()) { 

            HashMap.Entry entry = (HashMap.Entry)iter.next(); 

            FirstClassify classify = (FirstClassify) entry.getKey(); 

            urlInfos.addAll((ArrayList<UrlInfo>)entry.getValue()); // 二级分类添加到列表 

            mDbHelper.insertClassify(classify);// 将一级分类的数据保存在数据中 

          } 

      

          int count = urlInfos.size(); 

          for (int i = 0; i < count; i++) { 

            UrlInfo urlInfo = urlInfos.get(i); 

            mDbHelper.insertUrlInfo(urlInfo); 

            Log.i(TAG, "to dburlInfo.name=" + urlInfo.getName()); 

          } 

       } 

       /**

        * 设置默认显示的数据

        */ 

       private void setDefaultData(boolean editMode) { 

          Log.i(TAG, "setDefaultData"); 

          mClassifies = mDbHelper.queryClassify(); 

      

          getFirstClassifyDisplay(editMode); 

      

          int def = utils.getDefaultLoading(); 

      

          mUrlInfos = mDbHelper.queryUrlInfo(def); 

          if (mUrlInfos.isEmpty()) { 

            mUrlInfos = mDbHelper.queryUrlInfo(1); 

          } 

      

          getSecondDisplay(); 

       } 

      

       /**

        * 一级菜单显示

        *

        * @param editMode 是否编辑

        */ 

       private void getFirstClassifyDisplay(boolean editMode) { 

          FirstClassifyAdapteradapter = new FirstClassifyAdapter(mClassifies, 

               mContext, isEdit, this); 

          Log.i(TAG, "adapter.count=" + adapter.getCount()); 

          lstFirstClassify.setAdapter(adapter); 

          adapter.notifyDataSetChanged(); 

       } 

      

       /**

        * 得到默认设置的显示方式

        */ 

       private void getSecondDisplay() { 

          switch (utils.getDisplayStyle()) { 

          case Constant.GRID_DISPLAY: 

            SecondClassifyGridAdapter secondClassifyGridAdapter = newSecondClassifyGridAdapter(mUrlInfos, mContext); 

            gidSecondClassify.setAdapter(secondClassifyGridAdapter); 

            lstSecondClassify.setVisibility(View.GONE); 

            gidSecondClassify.setVisibility(View.VISIBLE); 

            secondClassifyGridAdapter.notifyDataSetChanged(); 

            break; 

          default: 

            SecondClassifyListAdapter secondClassifyAdapter = newSecondClassifyListAdapter(mUrlInfos, mContext); 

            lstSecondClassify.setAdapter(secondClassifyAdapter); 

            lstSecondClassify.setVisibility(View.VISIBLE); 

            gidSecondClassify.setVisibility(View.GONE); 

            secondClassifyAdapter.notifyDataSetChanged(); 

            break; 

          } 

       } 

        /**

        * 打开浏览器

        * @param url

        */ 

       private void openBrowser(String url){ 

          Intent intent = new Intent(); 

          intent.setAction("android.intent.action.VIEW"); 

          Uri content_uri_browsers = Uri.parse(url); 

          intent.setData(content_uri_browsers); 

       intent.setClassName("com.android.browser","com.android.browser.BrowserActivity"); 

          startActivity(intent); 

       } 

    </span></span> 

    对于列表的点击功能,上面我们说过通过实现OnItemClickListener,重写其中的方法

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">@Override 

        public void onItemClick(AdapterView<?> parent, View view, int position, 

                long id) { 

            UrlInfo urlInfo; 

            switch (parent.getId()) { 

            case R.id.list_first_classify: 

                FirstClassify firstClassify = (FirstClassify) lstFirstClassify.getItemAtPosition(position); // 得到当前点击的对象,这句代码关键 

                txtRightMsg.setText(firstClassify.getName()); 

                mUrlInfos = mDbHelper.queryUrlInfo(firstClassify.getId()); 

                getSecondDisplay(); 

                 

                break; 

            case R.id.second_classify_list: 

                urlInfo = (UrlInfo) lstFirstClassify.getItemAtPosition(position); 

                openBrowser(urlInfo.getUrl()); 

                break; 

            case R.id.second_classify_grid: 

                Log.i(TAG, "grid_context"); 

                urlInfo = (UrlInfo) gidSecondClassify.getItemAtPosition(position); 

                openBrowser(urlInfo.getUrl()); 

                break; 

            default: 

                break; 

            } 

        } 

    </span></span> 

        细心的读者又发现了,上面提到主要的方法中我们有调用到DBHelper类中相关的方法。是的,DBHelper是数据库相关操作的类,这里我列出来,给大家做个参考,类有待优化,还请各位别拍砖,呵呵

    DBHelper.java(Sqlite数据库操作类)

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">package org.winplus.surfing; 

    import java.util.ArrayList; 

    import android.R.integer; 

    import android.content.ContentValues; 

    import android.content.Context; 

    import android.database.Cursor; 

    import android.database.sqlite.SQLiteDatabase; 

    import android.database.sqlite.SQLiteOpenHelper; 

    import android.database.sqlite.SQLiteDatabase.CursorFactory; 

    import android.util.Log; 

    public class DBHelper { 

        private static final String TAG = "DBHelper"; 

        public static final String ID = "_id"; 

        public static final String NAME = "_name"; 

        public static final String URL = "_url"; 

        public static final String ICON = "_icon"; 

        public static final String DESCRIBE = "_describe"; 

        public static final String FKEY = "_fkey"; 

        public static final String FID = "_id"; 

        public static final String FNAME = "_name"; 

        public static final String FICON = "_icon"; 

        public static final int VERSION = 1; 

        public static final String DATABASE_NAME = "surfing.db";  

        public static final String TABLE_NAME_URLINFO = "urldata"; 

        public static final String TABLE_NAME_CLASSIFY = "classify"; 

        private static final String CREATE_TABLE_URLINFO = "create table " 

                + TABLE_NAME_URLINFO + "(" + ID 

                + " integer primary key autoincrement," + NAME + " text not null," 

                + URL + " text not null," + ICON + " blob," + DESCRIBE + "  text," 

                + FKEY + " integer)"; // 创建二级菜单关于URLinfo的表 

        private static final String CREATE_TABLE_CLASSFIY = "create table " 

                + TABLE_NAME_CLASSIFY + "(" + FID 

                + " integer primary key autoincrement," + FNAME + " text," + FICON 

                + " blob)"; // 创建一级菜单数据表 

        private SQLiteDatabase sqldb; 

        private Context mContext; 

        private ContentValues values; 

        private SurfingSQLHelper sqlHelper; 

        public DBHelper(Context context) { 

            mContext = context; 

        } 

        /**

         * 一个帮助类,用于创建和对数据库的版本进行管理

         * @author acer

         *

         */ 

        class SurfingSQLHelper extends SQLiteOpenHelper { 

            public SurfingSQLHelper() { 

                // 创建数据库和数据库版本号 

                super(mContext, DATABASE_NAME, null, VERSION); 

            } 

            /**

             * 此方法在第一次创建数据库的时候调用,一般我们用它来创建表

             */ 

            @Override 

            public void onCreate(SQLiteDatabase db) { 

                db.execSQL(CREATE_TABLE_URLINFO); 

                db.execSQL(CREATE_TABLE_CLASSFIY); 

            } 

            @Override 

            public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) { 

                // TODO Auto-generated method stub 

                db.execSQL("drop table if exists " + CREATE_TABLE_URLINFO); 

                db.execSQL("drop table if exists " + CREATE_TABLE_CLASSFIY); 

                onCreate(db); 

            } 

        } 

        /**

         * 打开数据库

         */ 

        public void open() { 

            sqlHelper = new SurfingSQLHelper(); 

            sqldb = sqlHelper.getWritableDatabase(); 

        } 

        /**

         * 关闭数据库

         */ 

        public void close() { 

            sqlHelper.close(); 

        } 

        /**

         * 添加网址信息

         * 

         * @param urlInfo

         * @return

         */ 

        public int insertUrlInfo(UrlInfo urlInfo) { 

            open(); 

            values = new ContentValues(); 

            values.put(NAME, urlInfo.getName()); 

            values.put(URL, urlInfo.getUrl()); 

            values.put(ICON, urlInfo.getIcon()); 

            values.put(DESCRIBE, urlInfo.getDescribe()); 

            values.put(FKEY, urlInfo.getFid()); 

            // empty是表中没有指向的列时用empty代替 

            int i = (int) sqldb.insert(TABLE_NAME_URLINFO, "empty", values); 

            close(); 

            return i; 

        } 

        /**

         * 添加一级分类记录

         * 

         * @param classify

         * @return

         */ 

        public int insertClassify(FirstClassify classify) { 

            open(); 

            Log.i(TAG, "insertClassify"); 

            values = new ContentValues(); 

            values.put(FNAME, classify.getName()); 

            Log.i(TAG, "classify.getIcon()=" + (classify.getIcon() == null)); 

            values.put(FICON, classify.getIcon());  

            // empty是表中没有指向的列时用empty代替 

            int i = (int) sqldb.insert(TABLE_NAME_CLASSIFY, "empty", values); 

            close(); 

            return i; 

        } 

        /**

         * 删除UrlInfo信息

         * 

         * @param id

         * @return

         */ 

        public int deleteUrlInfo(int id) { 

            open(); 

            String[] whereArgs = new String[] { String.valueOf(id) }; 

            int i = sqldb.delete(TABLE_NAME_URLINFO, "_id=?", whereArgs); 

            close(); 

            return i; 

        } 

        /**

         * 根据以及分类的ID删除其所有子类

         * 

         * @param fId

         * @return

         */ 

        public int deleteUrlInfoFid(int fId) { 

            open(); 

            String[] whereArgs = new String[] { String.valueOf(fId) }; 

            int i = sqldb.delete(TABLE_NAME_URLINFO, "_fkey=?", whereArgs); 

            close(); 

            return i; 

        } 

        /**

         * 删除Classify信息

         * 

         * @param id

         * @return

         */ 

        public int deleteClassify(int id) { 

            open(); 

            String[] whereArgs = { String.valueOf(id) }; 

            int i = sqldb.delete(TABLE_NAME_CLASSIFY, "_id=?", whereArgs); 

            close(); 

            return i; 

        } 

        /**

         * 根据ID修改网址记录

         * 

         * @param urlInfo

         * @return

         */ 

        public int updateUrlInfo(UrlInfo urlInfo) { 

            open(); 

            values = new ContentValues(); 

            values.put(NAME, urlInfo.getName()); 

            values.put(URL, urlInfo.getUrl()); 

            values.put(ICON, urlInfo.getIcon()); 

            values.put(DESCRIBE, urlInfo.getDescribe()); 

            values.put(FKEY, urlInfo.getFid()); 

            String[] whereArgs = { String.valueOf(urlInfo.getFid()) }; 

            int i = sqldb.update(TABLE_NAME_URLINFO, values, ID + "=?", whereArgs); 

            close(); 

            return i; 

        } 

        /**

         * 根据ID修改分类记录

         * 

         * @param classify

         * @return

         */ 

        public int updateClassify(FirstClassify classify) { 

            open(); 

            values = new ContentValues(); 

            values.put(FNAME, classify.getName()); 

            values.put(FICON, classify.getIcon()); 

            String[] whereArgs = { String.valueOf(classify.getId()) }; 

            int i = sqldb.update(TABLE_NAME_CLASSIFY, values, ID + "=?", whereArgs); 

            return i; 

        } 

        /**

         * 根据ID查询UrlInfo

         * 

         * @return

         */ 

        public ArrayList<UrlInfo> queryUrlInfo(int id) { 

            open(); 

            String[] whereArgs = new String[] { String.valueOf(id) }; 

            Cursor cursor = sqldb.query(TABLE_NAME_URLINFO, null, "_fkey=?", 

                    whereArgs, null, null, null, null); 

            ArrayList<UrlInfo> urlInfos = new ArrayList<UrlInfo>(); 

            int count = cursor.getCount(); 

            for (int i = 0; i < count; i++) { 

                cursor.moveToPosition(i); 

                UrlInfo urlBean = new UrlInfo(); 

                urlBean.setFid(cursor.getInt(cursor.getColumnIndex(ID))); 

                urlBean.setName(cursor.getString(cursor.getColumnIndex(NAME))); 

                urlBean.setUrl(cursor.getString(cursor.getColumnIndex(URL))); 

                urlBean.setIcon(cursor.getBlob(cursor.getColumnIndex(ICON))); 

                urlBean.setDescribe(cursor.getString(cursor 

                        .getColumnIndex(DESCRIBE))); 

                urlInfos.add(urlBean); 

            } 

            close(); 

            return urlInfos; 

        } 

        /**

         * 查询所有的Url分类

         * 

         * @return

         */ 

        public ArrayList<FirstClassify> queryClassify() { 

            open(); 

            Cursor cursor = sqldb.query(TABLE_NAME_CLASSIFY, null, null, null, 

                    null, null, null); 

            ArrayList<FirstClassify> classifies = new ArrayList<FirstClassify>(); 

            int count = cursor.getCount(); 

            for (int i = 0; i < count; i++) { 

                cursor.moveToPosition(i); 

                FirstClassify classify = new FirstClassify(); 

                classify.setId(cursor.getInt(cursor.getColumnIndex(FID))); 

                classify.setName(cursor.getString(cursor.getColumnIndex(FNAME))); 

                classify.setIcon(cursor.getBlob(cursor.getColumnIndex(FICON))); 

                classifies.add(classify); 

            } 

            close(); 

            return classifies; 

        } 

    </span></span> 

    Ok,项目写到这里,主要功能差不多完成了。当然还有很多需要优化的细节,比如说图片,比如说代码、UI优化工作,先上效果图看看(这只是初步的实现主要功能)

        下面,我们再SurfingDemoActivity.java中添加一个Menu,首先在res目录下添加一个名为menu的文件夹,再新建一个名为surfing_menu.xml的xml文件。

    surfing_menu.xml(Menu)

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?> 

    <menu xmlns:android="http://schemas.android.com/apk/res/android" > 

        <item  

            android:id="@+id/menu_add"  

            android:icon="@drawable/ic_launcher"  

            android:title="添加"/> 

        <item  

            android:id="@+id/menu_edit"   

            android:icon="@drawable/ic_launcher"  

            android:title="编辑"/>  

        <item  

            android:id="@+id/menu_default"   

            android:icon="@drawable/ic_launcher"  

            android:title="恢复默认"/> 

    </menu> 

    </span></span> 

    在android中实现Menu的功能很简单,只需要重写onCreateOptionsMenu方法就好了,点击菜单执行相应的操作重写:onOptionsItemSelected。这里,我直接引用上面的xml文件来实现menu的布局。当然你可以直接在代码里面写。引用也很简单,就一句话:

    <span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">this.getMenuInflater().inflate(R.menu.surfing_menu, menu);</span></span> 

    上面我们提到在菜单中有编辑功能。比如说添加一级菜单,二级菜单,这些都是些简单的逻辑处理,这里就不再贴代码了。最后我就编辑中选择图片的功能做一个简单的介绍,这个类似于一个简单的文件浏览器。我们先看看界面

        其实实现这个功能是很简单的。先获取设备的目录地址,枚举此目录下所有的文件,然后显示在界面上,如果是图片,就直接将图片的略缩图显示出来。点击图片就直接获取其地址,返回给设置界面,赋值给EditText就可以了。这里我贴出关键代码,作为参考,很有改善余地的。呵呵,各位见谅了…

    BrowserFileAdapter.java这个是继承BaseAdapter的,没有什么好说的。

    <div style="font-weight: normal; text-align: left; "><span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">package org.winplus.surfing;</span></span></div><span style="font-size:16px;"><span style="font-family: 'Microsoft YaHei'; "></span></span><div style="text-align: left;"><span style="font-weight: normal;"><span style="font-size:16px;"> 

    </span></span></div><span style="font-size:16px;"><span style="font-weight: normal;"></span></span><div style="text-align: left;"><span style="font-size:16px;">import java.util.ArrayList;</span></div><div style="text-align: left;"><span style="font-family: 'Microsoft YaHei'; "><span style="font-weight: normal; "><span style="font-size:16px;"> 

    </span></span></span></div><span style="font-size:16px;"><span style="font-family: 'Microsoft YaHei'; "></span></span><div style="text-align: left;"><span style="font-weight: normal;"><span style="font-size:16px;"> 

    </span></span></div><span style="font-size:16px;"><span style="font-weight: normal;"></span></span><div style="text-align: left;"><span style="font-size:16px;">import android.content.Cont</span></div><div style="text-align: left;"><span style="font-size:16px;">ext;</span></div><div style="text-align: left;"><span style="font-size:16px;">t android.view.LayoutInflater; 

    impo</span></div><div style="text-align: left;"><span style="font-size:16px;">impo</span></div><div style="text-align: left;"><span style="font-size:16px;">rrt android.view.View;</span></div><div style="text-align: left;"><span style="font-size:16px;">wGroup; 

    import android.widget.</span></div><div style="text-align: left;"><span style="font-size:16px;">import android.view.Vi</span></div><div style="text-align: left;"><span style="font-size:16px;">eBaseAdapter;</span></div><div style="text-align: left;"><span style="font-size:16px;">import android.widget.ImageView;</span></div><div style="text-align: left;"><span style="font-size:16px;">rowserFileAdapter extends BaseA</span></div><div style="text-align: left;"><span style="font-size:16px;">import android.widget.TextView;</span></div><span style="font-size:16px;"> 

    </span><div style="text-align: left;"><span style="font-size:16px;">ublic class </span></div><span style="font-size:16px;"> 

    </span><div style="text-align: left;"><span style="font-size:16px;">Bdapter {</span></div><span style="font-size:16px;"> 

    </span><div style="text-align: left;"><span style="font-size:16px;">rayList<DirFileInfo> mDirFileInfos = new ArrayList<DirFileInfo>(); 

        private L</span></div><span style="font-size:16px;">  private A 

    rayoutInflater inflater; 

    </span><div style="text-align: left;"><span style="font-size:16px;">(ArrayList<DirFileInfo> dirFileInfos,Context context) { 

            mDirFileInfos = dirFile</span></div><span style="font-size:16px;">    public BrowserFileAdapte 

    rInfos; 

            inflater = LayoutInflater.from(context); 

        } 

        @Override 

        public int getCount() { 

    </span><div style="text-align: left;"><span style="font-size:16px;">rride 

        public Object getItem(int </span></div><span style="font-size:16px;">     if (!mDirFileInfos.isEmpty()) { 

                return mDirFileInfos.size(); 

            } 

            return 0; 

        } 

        @Ov 

    eposition) { 

            if (!mDirFileInfos.isEmpty()) { 

                return mDirFileInfos.get(position); 

            } 

            return null; 

        } 

        @Override 

    </span><div style="text-align: left;"><span style="font-size:16px;">iewGroup parent) { 

            ViewHolder  holde</span></div><span style="font-size:16px;">  public long getItemId(int position) { 

            return 0; 

        } 

        @Override 

        public View getView(int position, View convertView,  

    Vr  = null; 

            DirFileInfo dirFileInfo = mDirFileInfos.get(position); 

            if (convertView == null) { 

                holder = new ViewHolder(); 

    </span><div style="text-align: left;"><span style="font-size:16px;">.list_photo_icon); 

                holder.name = (TextView) convertView.findViewBy</span></div><span style="font-size:16px;">            convertView = inflater.inflate(R.layout.list_photo_browser, null); 

                holder.icon = (ImageView) convertView.findViewById(R.i 

    dId(R.id.list_photo_name); 

                convertView.setTag(holder); 

            }else { 

                holder = (ViewHolder) convertView.getTag(); 

            } 

             

            holder.icon.setBackgroundDrawable(dirFileInfo.getIcon()); 

    </span><div style="text-align: left;"></div><span style="font-size:16px;">      holder.name.setText(dirFileInfo.getName()); 

             

            return convertView; 

        } 

        private class ViewHolder{ 

            public ImageView icon; 

            public TextView name; 

        } 

    </span><div style="text-align: left;"></div><span style="font-size:16px;">} 

    </span> 

    DirFileInfo.java(目录文件信息)

    <span style="font-weight: normal;"><span style="font-family: 'Microsoft YaHei'; "><span style="font-size:16px;">public class DirFileInfo{ 

      

       private Drawable icon; 

       private String name; 

       private String path; 

      private boolean isDir; 

    // getter。Setter 

    }</span></span></span> 

    BrowserPhotoActivity.java(Activity显示界面)

    <span style="font-size:16px;">package org.winplus.surfing; 

    import java.util.ArrayList; 

    import android.app.Activity; 

    import android.content.Context; 

    import android.content.Intent; 

    import android.os.Bundle; 

    import android.os.Environment; 

    import android.util.Log; 

    import android.view.Gravity; 

    import android.view.View; 

    import android.view.View.OnClickListener; 

    import android.view.ViewGroup.LayoutParams; 

    import android.widget.AdapterView; 

    import android.widget.Button; 

    import android.widget.EditText; 

    import android.widget.GridView; 

    import android.widget.ImageView; 

    import android.widget.LinearLayout; 

    import android.widget.ListView; 

    import android.widget.AdapterView.OnItemClickListener; 

    public class BrowserPhotoActivity extends Activity implements OnClickListener, 

            OnItemClickListener { 

        private static final String TAG = "BrowserPhotoActivity"; 

        private EditText edtPath; 

    //  private Button btnList; 

    //  private Button btnGrid; 

        private ListView lstFile; 

        private GridView gidFile; 

        private Context mContext; 

        private BrowserFileAdapter fileAdapter; 

        private ArrayList<DirFileInfo> dirFileInfos = new ArrayList<DirFileInfo>(); 

        private static StringBuffer mPath = new StringBuffer(); 

        @Override 

        protected void onCreate(Bundle savedInstanceState) { 

            super.onCreate(savedInstanceState); 

            setContentView(R.layout.photo_browser); 

            setupViews(); 

        } 

        private void setupViews() { 

            edtPath = (EditText) findViewById(R.id.photo_path); 

            //btnGrid = (Button) findViewById(R.id.photo_grid_display); 

            //btnList = (Button) findViewById(R.id.photo_list_display); 

            lstFile = (ListView) findViewById(R.id.photo_list); 

            gidFile = (GridView) findViewById(R.id.photo_grid); 

            gidFile.setVisibility(View.GONE); 

            //btnGrid.setOnClickListener(this); 

            //btnList.setOnClickListener(this); 

            lstFile.setOnItemClickListener(this); 

             

            mContext = BrowserPhotoActivity.this; 

             

            // 添加表头 

            LinearLayout layout=new LinearLayout(mContext); 

            layout.setGravity(Gravity.LEFT); 

            ImageView imageView = new ImageView(mContext); 

            imageView.setBackgroundResource(R.drawable.ic_prev_dir); 

            layout.addView(imageView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 

            lstFile.addHeaderView(layout); 

             

            mPath.append(Environment.getExternalStorageDirectory().getPath()); 

            getCurrentPathFileDir(mPath.toString()); 

        } 

        @Override 

        public void onClick(View v) { 

            switch (v.getId()) { 

            case R.id.photo_grid: 

                break; 

            case R.id.photo_list: 

                break; 

            default: 

                break; 

            } 

        } 

        @Override 

        public void onItemClick(AdapterView<?> parent, View view, int position, 

                long id) { 

            if (position==0) { 

                 

                int pathCount = mPath.toString().lastIndexOf("/"); 

                if (pathCount==0) { 

                    mPath.delete(0, mPath.length()).append("/"); 

                }else { 

                    mPath.delete(pathCount,mPath.length()); 

                } 

                 

                getCurrentPathFileDir(mPath.toString()); 

            }else { 

                DirFileInfo dirFileInfo = (DirFileInfo) lstFile.getItemAtPosition(position);// 得到当前点击的对象 

                if(dirFileInfo.isDir()){ 

                    // 为目录时,点击显示下级目录中的图片文件和文件夹 

                    Log.i(TAG, "is dir"); 

                    Log.i(TAG, "mpath="+mPath+";dirFileInfo.name="+dirFileInfo.getName()); 

                    mPath.append("/").append(dirFileInfo.getName()); 

                    getCurrentPathFileDir(mPath.toString()); 

                }else { 

                    // 选择图片 

                    Intent intent = new Intent(); 

                    Log.i(TAG, "dirFileInfo.getPath()="+dirFileInfo.getPath()); 

                    intent.putExtra("iconAbsPath",dirFileInfo.getPath()); 

                    setResult(Constant.BROWSER_IMAGE, intent); 

                    finish(); 

                    Log.i(TAG, "is file"); 

                } 

            } 

             

        } 

         

        /**

         * 得到当前目录下的图片文件和文件夹

         * @param path

         */ 

        private void getCurrentPathFileDir(String path){ 

            edtPath.setText(path); 

            dirFileInfos = Utils.getDirFile(path, mContext); 

            fileAdapter = new BrowserFileAdapter(dirFileInfos, mContext); 

            lstFile.setAdapter(fileAdapter); 

            fileAdapter.notifyDataSetChanged(); 

        } 

    </span> 

    五、扩展,我们在界面设计中看到有一个排序功能,这是一个扩展的功能,其实在这个应用程序中可以不做,意义不大,如果非要做这个功能的话,我建议做成Music中能够通过拖动列表进行排序这样的功能

  • 相关阅读:
    [转]史上最最佳软件开发实践指导
    附微信表情代码关键字
    TFS2010配置篇 — 万事具备只欠外网访问
    ASP.NET在IE10中Form表单身份验证失效问题解决方法
    TFS2010 更改已经登录的用户
    TFS命令tf:undo 强制签入别人签出的文件
    ASP.NET Core部署后报错: Could not load file or assembly 'System.Private.ServiceModel, Version=4.1.2.4, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
    SQL 增删改查语句
    存储过程拼接sql
    sql 求时间差
  • 原文地址:https://www.cnblogs.com/android-blogs/p/4891076.html
Copyright © 2011-2022 走看看