zoukankan      html  css  js  c++  java
  • 【读书笔记-《Android游戏编程之从零开始》】6.Android 游戏开发常用的系统控件(TabHost、ListView)

    3.9 TabSpec与TabHost

    TabHost类官方文档地址:http://developer.android.com/reference/android/widget/TabHost.html

    Android 实现tab视图有2种方法,一种是在布局页面中定义<tabhost>标签,另一种就是继承tabactivity.但是我比较喜欢第二种方式,应为如果页面比较复杂的话你的XML文件会写得比较庞大,用第二种方式XML页面相对要简洁得多。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/mm1"
        android:orientation="vertical" >
    
        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="第一个Tab" />
    
        <EditText
            android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="第二个Tab" />
    
        <LinearLayout
            android:id="@+id/myLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/mm2"
            android:orientation="vertical" >
    
            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="第三个Tab" />
    
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="第三个Tab" />
        </LinearLayout>
    
    </LinearLayout>
    activity_main.xml 
    import android.app.TabActivity;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.widget.TabHost;
    import android.widget.TabHost.OnTabChangeListener;
    import android.widget.TabHost.TabSpec;
    import android.widget.Toast;
    
    public class MainActivity extends TabActivity implements OnTabChangeListener {
        private TabSpec ts1, ts2, ts3;// 声明3个分页
        private TabHost tabHost;// 分页菜单(tab容器)
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tabHost = this.getTabHost();// 实例(分页)菜单
            // 利用LayoutInflater将布局与分页菜单一起显示
            LayoutInflater.from(this).inflate(R.layout.activity_main,
                    tabHost.getTabContentView());
            ts1 = tabHost.newTabSpec("tabOne");// 实例化一个分页
            ts1.setIndicator("分页1");// 设置此分页显示的标题
            ts1.setContent(R.id.btn);// 设置此分页的资源Id
            ts2 = tabHost.newTabSpec("tabTwo");
            // 设置此分页显示的标题和图标
            ts2.setIndicator("分页2",
                    getResources().getDrawable(R.drawable.ic_launcher));
            ts2.setContent(R.id.et);
            ts3 = tabHost.newTabSpec("tabThree");
            ts3.setIndicator("分页3");
            ts3.setContent(R.id.myLayout);// 设置此分页的布局ID
            tabHost.addTab(ts1);// 菜单中添加ts1分页
            tabHost.addTab(ts2);
            tabHost.addTab(ts3);
            tabHost.setOnTabChangedListener(this);
        }
    
        @Override
        public void onTabChanged(String tabId) {
            //这里的tabId对应的是实例中每个分页传入的分页ID,而不是TabSpec.setIndicator()设置的标题
            if (tabId.equals("tabOne")) {
                Toast.makeText(this, "分页1", Toast.LENGTH_SHORT).show();
            }
            if (tabId.equals("tabTwo")) {
                Toast.makeText(this, "分页2", Toast.LENGTH_SHORT).show();
            }
            if (tabId.equals("tabThree")) {
                Toast.makeText(this, "分页3", Toast.LENGTH_SHORT).show();
            }
        }
    
    }
    MainActivity.class

    上面这个Activity继承了TabActivity

    TabActivity的现状

    官方文档在介绍TabActivity有下面这么一句话

    大概的意思是说:这个类已经在Android4.0的系统中被弃用了,新的应用程序应该使用Fragment来代替该类的开发

    TabActivity是否还有存在的必要性

    其实谷歌有此举动,我们也应该早就想到了,为什么会这么说呢?那就要从TabActivity的原理开始说起了。

    做个假定先: 比如我们最外面的Activity是MainActivity, 第一个tab是FirstActivty, 第二个tab是SecondActivity。
    相信大家都用过TabActivity, 它是一个特殊的Activity,它特殊的地方在哪里?有以下几点为证:
    <1> 它看起来违反了Activity的单一窗口的原则。因为它可以同时加载几个activity, 当用户点击它上面的tab时,就会跳到相应的Activity上面去。
    <2> 用户首先进去FirstActivity,然后进去SecondActivity,再点击返回键的时候。它返回的界面不是FirstActivity,而是退出我们的应用程序。
    <3> 当用户在FirstActivity按返回键的时候,如果MainActivity和FirstActivity通过重写onKeyDown()方法,那么收到事件回调的只有FirstActivity。

    谷歌当时的困扰

    <1> 首先我们要明白一点,android系统是单窗口系统,不像windows是多窗口的(比如在windows系统上,我们可以一边聊QQ,一边斗地主等等)。也就是说,在一个时刻,android里面只有一个activity可以显示给用户。这样就大大降低了操作系统设计的复杂性(包括事件派发等等)。
    <2> 但是像TabActivity那种效果又非常必要,用户体验也比较好。所以我觉得当时google开发人员肯定很纠结,于是,一个畸形的想法产生了,就是在单窗口系统下加载多个activity,它就是TabActivity。

    TabActivity实现加载多个Activity原理

    我们都知道,想启动一个Activity,一般是调用startActivty(Intent i)方法,然后这个方法会辗转调用到ams(ActivityManagerService)来启动目标activity,所以,TabActivity实现的要点有两个:
    <1> 找到一个入口,这个入口可以访问到ActivityThread类(这个类是隐藏的,应用程序是访问不到的),然后调用ActivityThread里面的启动activity方法
    <2> 绕开ams,就是我们TabActivity加载的FirstActivity和SecondActivity是不能让ams知道的。

    所以,一个新的类诞生了 ---- LocalActivityManager , 它的作用如下:
    <1> 这个类和ActivityThread处于一个包内,所以它有访问ActivityThread的权限。
    <2> 这个类提供了类似Ams管理Activity的方法,比如调用activity的onCreate方法,onResume()等等,维护了activity生命周期。

    也正如其名字一样,它是本地的activity管理。就是说它运行的进程和它管理的Activity是在一个进程里面。所以,当TabActivity要启动一个activity的时候,会调用到LocalActivityManager的创建activity方法,然后调用ActivityThread.startActivityNow(),这个方法绕过了ams,就是说ams此时根本不知道LocalActivityManager已经在暗渡陈仓的启动了一个activity(所以ams的task列表里面没有新启动activity的记录,所以用户按back键就直接退出我们的应用)。然后和正常启动activity一样,初始化activity,在初始化activity的时候,有个方法非常重要:activity.attch()

    final void attach(...){  
    ....  
    mWindow.setCallback(this);    
    .....  
    }  

     mWindow.setCallback(this)这个方法非常重要,它设置了window的回调接口,这是我们activity能够接受到key事件的关键所在!因为在DecorView在接受到事件的时候,会回调这个接口,如:

    final Callback cb = getCallback();  
    final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); 

    当我们启动FirstActivity的时候,我们设置FirstActivity为PhoneWindow的回调实现,所以,按back键的时候,调用的是FirstActivity的onKeyDown方法。

    TabActivity小结

    从以上的种种分析来看,TabActivity只是一个怪胎而已。所以,在后面的发展中肯定会被代替,只是没想到会被替代的这么快。不经让我有了一种英雄暮路,美人辞暮的感觉,至少TabActivity曾经在Android2.2/2.3版本那么显赫一时,不过终究还是逃不过被谷歌遗弃的命运。

    TabActivity实现方法

    说了这么多,那就让我们来看看它当年到底是怎样的叱咤风云,我们将使用两种不同的方式来实现,但是最终的效果都是一样的,

    如下图所示:

    具体的编码实现

    (1)第一种实现方式:自定义TabWidget

    1、首先创建一个TabWidget的布局文件,main_tab_layout1.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/tabhost" 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        
        <LinearLayout 
            android:orientation="vertical"
            android:layout_width="fill_parent" 
            android:layout_height="fill_parent">
    
            <FrameLayout 
                android:id="@android:id/tabcontent"
                android:layout_width="fill_parent" 
                android:layout_height="0.0dip"
                android:layout_weight="1.0" />
                
            <TabWidget 
                android:id="@android:id/tabs" 
                android:layout_width="fill_parent" 
                android:layout_height="wrap_content"
                android:padding="2dip"
                android:background="@drawable/tab_widget_background"
                android:layout_weight="0.0"/>
                
        </LinearLayout>
        
    </TabHost>
    main_tab_layout1.xml
    注意: 
         <1> 不管你是使用TabActivity 还是自定义TabHost,都要求以TabHost作为XML布局文件的根;
         <2> 将FrameLayout的属性值layout_weight设置为了1.0,这样就可以把TabWidget的组件从顶部挤了下来变成了底部菜单栏。
         <3> <TabWidger> 和<FrameLayout>的Id 必须使用系统id,分别为android:id/tabs 和 android:id/tabcontent 。因为系统会使用者两个id来初始化TabHost的两个实例变量(mTabWidget 和 mTabContent)。
    2、然后在定义一个tab_item_view.xml布局文件,这个布局文件在后面初始化Tab按钮的时候会用到 
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical" >
    
        <ImageView
            android:id="@+id/imageview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:focusable="false"
            android:padding="3dp" >
        </ImageView>
    
        <TextView
            android:id="@+id/textview"
            style="@style/tab_item_text_style"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
        </TextView>
    
    </LinearLayout>
    tab_item_view.xml

    3、这里我为了方便Tab按钮字体和背景格式的统一,在styles.xml数据文件中还添加了以下内容:

        <style name="tab_item_text_style">
            <item name="android:textSize">10.0dip</item>
            <item name="android:textColor">#ffffff</item>
            <item name="android:ellipsize">marquee</item>
            <item name="android:singleLine">true</item>
        </style>
    
        <style name="tab_item_background">
            <item name="android:textAppearance">@style/tab_item_text_style</item>
            <item name="android:gravity">center_horizontal</item>
            <item name="android:background">@drawable/selector_tab_background2</item>
            <item name="android:layout_width">fill_parent</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:button">@null</item>
            <item name="android:drawablePadding">3.0dip</item>
            <item name="android:layout_weight">1.0</item>
        </style>

    4、定义一个自定义Tab按钮资源文件,selector_tab_background.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:drawable="@drawable/tab_item_p" android:state_pressed="true"/>
        <item android:drawable="@drawable/tab_item_d" android:state_selected="true"/>
    
    </selector>

    5、最后在定义几个用来存放Tab选项卡内容的activity布局文件,由于几个布局文件的内容都差不多,所以这里就列出一个给读者参考,有需要的话可以直接下载源码,layout_activity1.xml:

    <?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">
    
        <ImageView
            android:id="@+id/imageview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:scaleType="fitCenter"
            android:src="@drawable/mm1" >
        </ImageView>
    
    </LinearLayout>

    6、布局完毕,接下来讲解java代码,定义一个常量工具类,Constant.java:

    /**
     *    功能描述:常量工具类
     */
    public class Constant {
        
        public static final class ConValue{
            
            /**
             * Tab选项卡的图标
             */
            public static int   mImageViewArray[] = {R.drawable.tab_icon1,
                                                     R.drawable.tab_icon2,
                                                     R.drawable.tab_icon3,
                                                     R.drawable.tab_icon4,
                                                     R.drawable.tab_icon5};
    
            /**
             * Tab选项卡的文字
             */
            public static String mTextviewArray[] = {"主页", "关于", "设置", "搜索", "更多"};
            
            
            /**
             * 每一个Tab界面
             */
            public static Class mTabClassArray[]= {Activity1.class,
                                                   Activity2.class,
                                                   Activity3.class,
                                                   Activity4.class,
                                                   Activity5.class};
        }
    }
    Constant.java
    7、定义自定义Tab选项卡Activity类,在这个类中我们可以采用两种方法编写标签页:
    <1> 第一种是继承TabActivity ,然后使用getTabHost()获取TabHost对象;
    <2> 第二种方法是使用自定的TabHost在布局文件上<TabHost>的自定义其ID,然后通过findViewById(),方法获得TabHost对象。 
    本文中采用是继承TabActivity的方法,TabActivity1.java:
    package com.example.hiyou;
    
    import android.app.TabActivity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TabHost;
    import android.widget.TabHost.TabSpec;
    import android.widget.TextView;
    
    import com.example.hiyou.Constant.ConValue;
    /**
     *    功能描述:第一种实现方法,自定义TabHost
     */
    public class TabActivity1 extends TabActivity {
        //定义TabHost对象
        private TabHost    tabHost;    
        //定义一个布局
        private LayoutInflater layoutInflater;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
               setContentView(R.layout.main_tab_layout1);            
                initView();
        }
    
        /**
         * 初始化组件
         */
        private void initView(){
            //实例化TabHost对象,得到TabHost
            tabHost = getTabHost();
            
            //实例化布局对象
            layoutInflater = LayoutInflater.from(this);
            
            //得到Activity的个数
            int count = ConValue.mTabClassArray.length;    
                    
            for(int i = 0; i < count; i++){    
                //为每一个Tab按钮设置图标、文字和内容
                TabSpec tabSpec = tabHost.newTabSpec(ConValue.mTextviewArray[i]).setIndicator(getTabItemView(i)).setContent(getTabItemIntent(i));
                //将Tab按钮添加进Tab选项卡中
                tabHost.addTab(tabSpec);
                //设置Tab按钮的背景
                tabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.selector_tab_background);
            }
        }
                
        /**
         * 给Tab按钮设置图标和文字
         */
        private View getTabItemView(int index){
            View view = layoutInflater.inflate(R.layout.tab_item_view, null);
        
            ImageView imageView = (ImageView) view.findViewById(R.id.imageview);
    
            if (imageView != null){
                imageView.setImageResource(ConValue.mImageViewArray[index]);
            }        
            TextView textView = (TextView) view.findViewById(R.id.textview);
            
            textView.setText(ConValue.mTextviewArray[index]);
        
            return view;
        }
        
        /**
         * 给Tab选项卡设置内容(每个内容都是一个Activity)
         */
        private Intent getTabItemIntent(int index){
            Intent intent = new Intent(this, ConValue.mTabClassArray[index]);
            
            return intent;
        }
    }
    TabActivity1.java
    这段代码比较复杂,我们需要详细分析一下:
      <1> 首先需要做的是获取TabHost对象,可以通过TabActivtiy里的getTabHsot()方法;
      <2> 接着向TabHost添加tabs.即调用tabHost.addTab(TabSpec) 方法。TabSpec主要包含了setIndicator 和 setContent 方法,通过这两个方法来指定Tab 和 TanContent;
      <3> TabSpec 通过 .newTabSpec(String tag)来创建实例。实例化后对其属性进行设置。setIndicator()设置tab,它有3个重载的函数:
    • public TabHost.TabSpec  setIndicatior(CharSwquence label,Drawable icon).指定tab的标题和图标。
    • public TabHost.TabSpec (View view)通过View来自定义tab
    • public TabHost.TabSpec(CharSequence label) 指定tab的标题,此时无图标。
      <4> setContent 指定tab的展示内容,它也有3种重载:
    • public TabHost.TabSpec setContent(TabHost.TabContentFactory )
    • public TabHost.TabSpec setContent(int ViewId)
    • public TabHost.TabSpec setContent(Intent intent)  
         后两种方法比较后理解一个是通过 ViewId指定显示的内容,如.setContent(R.id.Team_EditText),第三种则是直接通过Intent加载一个新的Activity页。如.setContent(new Intent(this, MeetingActivity.class)));
    8、最后再定义Tab选项卡内容的Activity,显示对应的布局页面就行了,这里只列出一个,Activity1.java:
    package com.example.hiyou;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class Activity1 extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.layout_activity1);
        }
    }
    (二)第二中实现方式:隐藏TabWidget,通过RadioGroup和RadioButton实现底部菜单栏

    这种方式更漂亮,也更灵活,大部分的应用程序基本都是使用这种方式,通过setCurrentTabByTag()方法来切换不同的选项卡。 

    1、首先创建一个布局界面,main_tab_layout2.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/tabhost"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical" >
    
            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="fill_parent"
                android:layout_height="0.0dip"
                android:layout_weight="1.0" />
    
            <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_weight="0.0"
                android:visibility="gone" />
    
            <RadioGroup
                android:id="@+id/main_radiogroup"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:background="@drawable/tab_widget_background"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:padding="2dip" >
    
                <RadioButton
                    android:id="@+id/RadioButton0"
                    style="@style/tab_item_background"
                    android:drawableTop="@drawable/tab_icon1"
                    android:text="主页" 
                    android:textColor="#ffffff"/>
    
                <RadioButton
                    android:id="@+id/RadioButton1"
                    style="@style/tab_item_background"
                    android:drawableTop="@drawable/tab_icon2"
                    android:text="关于" 
                    android:textColor="#ffffff"/>
    
                <RadioButton
                    android:id="@+id/RadioButton2"
                    style="@style/tab_item_background"
                    android:drawableTop="@drawable/tab_icon3"
                    android:text="设置" 
                    android:textColor="#ffffff"/>
    
                <RadioButton
                    android:id="@+id/RadioButton3"
                    style="@style/tab_item_background"
                    android:drawableTop="@drawable/tab_icon4"
                    android:text="搜索" 
                    android:textColor="#ffffff"/>
    
                <RadioButton
                    android:id="@+id/RadioButton4"
                    style="@style/tab_item_background"
                    android:drawableTop="@drawable/tab_icon5"
                    android:text="更多" 
                    android:textColor="#ffffff"/>
            </RadioGroup>
        </LinearLayout>
    
    </TabHost>
    main_tab_layout2.xml

    2、然后在定义几个用来存放Tab选项卡内容的activity布局文件,同上activity1_layout.xml。

    3、最后再定义一个自定义Tab按钮的资源文件,selector_tab_background2.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:drawable="@drawable/tab_item_p" android:state_pressed="true"/>
        <item android:drawable="@drawable/tab_item_d" android:state_checked="true"/>
    
    </selector>

    4、布局界面讲解完毕,接下来详细讲解java代码

    package com.example.hiyou;
    
    import android.app.TabActivity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.widget.RadioButton;
    import android.widget.RadioGroup;
    import android.widget.RadioGroup.OnCheckedChangeListener;
    import android.widget.TabHost;
    import android.widget.TabHost.TabSpec;
    
    import com.example.hiyou.Constant.ConValue;
    /**
     *    功能描述:第二种实现方式,自定义RadioGroup
     */
    public class TabActivity2 extends TabActivity {
    
        //定义TabHost对象
        private TabHost tabHost;
        
        //定义RadioGroup对象
        private RadioGroup radioGroup;
        
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_tab_layout2);
            
            initView();
            
            initData();
        }
        
        /**
         * 初始化组件
         */
        private void initView(){
            //实例化TabHost,得到TabHost对象
            tabHost = getTabHost();
            
            //得到Activity的个数
            int count = ConValue.mTabClassArray.length;                
                    
            for(int i = 0; i < count; i++){    
                //为每一个Tab按钮设置图标、文字和内容
                TabSpec tabSpec = tabHost.newTabSpec(ConValue.mTextviewArray[i]).setIndicator(ConValue.mTextviewArray[i]).setContent(getTabItemIntent(i));
                //将Tab按钮添加进Tab选项卡中
                tabHost.addTab(tabSpec);
            }
            
            //实例化RadioGroup
            radioGroup = (RadioGroup) findViewById(R.id.main_radiogroup);
        }
        
        /**
         * 初始化组件
         */
        private void initData() {
            // 给radioGroup设置监听事件
            radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup group, int checkedId) {
                    switch (checkedId) {
                    case R.id.RadioButton0:
                        tabHost.setCurrentTabByTag(ConValue.mTextviewArray[0]);
                        break;
                    case R.id.RadioButton1:
                        tabHost.setCurrentTabByTag(ConValue.mTextviewArray[1]);
                        break;
                    case R.id.RadioButton2:
                        tabHost.setCurrentTabByTag(ConValue.mTextviewArray[2]);
                        break;
                    case R.id.RadioButton3:
                        tabHost.setCurrentTabByTag(ConValue.mTextviewArray[3]);
                        break;
                    case R.id.RadioButton4:
                        tabHost.setCurrentTabByTag(ConValue.mTextviewArray[4]);
                        break;
                    }
                }
            });
            ((RadioButton) radioGroup.getChildAt(0)).toggle();
        }
        
        /**
         * 给Tab选项卡设置内容(每个内容都是一个Activity)
         */
        private Intent getTabItemIntent(int index){
            Intent intent = new Intent(this, ConValue.mTabClassArray[index]);    
            return intent;
        }
    
    }
    TabActivity2.java

    5、最后再定义Tab选项卡内容的Activity,同上Activity1.java。

    源代码下载:HiYou.zip

    资料来源:【Android UI设计与开发】第06期:底部菜单栏(一)使用TabActivity实现底部菜单栏

    3.10 ListView

     ListView类官方文档地址:http://developer.android.com/reference/android/widget/ListView.html

    ListView(列表视图)是一个常用的组件,ListView里面的每个子项Item可以是一个字符串,也可以是一个组合控件。其数据内容以列表形式直接展示出来,比如做一个游戏的排行榜,对话列表等等都可以使用列表来实现,且ListView的优点是列表中的数据可以自适应屏幕大小。

    在android中,由于数据来源多种多样,如从资源文件读取、从数据库中读取、从网络上其他地方读取,而最终这些数据都将被展示在ListView中,所以android就用adapter设计模式,对应每种数据来源使用对应的adapter来连接数据和视图。Adapter就是数据和视图之间的桥梁,数据在adapter中做处理,然后显示到ListView上面。

    下面主要介绍三种adapter:ArrayAdapter<T>SimpleAdapter和SimpleCursorAdapter。
    1.ArrayAdapter<T>:最简单的适配器

    ArrayAdapter类官方文档地址:http://developer.android.com/reference/android/widget/ArrayAdapter.html

    首先创建存放ListView的Activity所需要的布局activity_main.xml文件。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        tools:context=".MainActivity" >  
      
        <ListView  
            android:id="@+id/list"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" />  
    </LinearLayout>  

    上面代码创建了一个布局配置文件,里面只放了一个ListView控件,将其ID设置为:list。
    接下来是list_item.xml,用来设置ListView中每个Item的布局,是ListItem的XML实现。
    Android提供了多种ListItem的Layout (R.layout),以下是较为常用的:

       android.R.layout.simple_list_item_1                 //一行text
       android.R.layout.simple_list_item_2                 //一行title,一行text
       android.R.layout.simple_list_item_single_choice     //单选按钮
       android.R.layout.simple_list_item_multiple_choice   //多选按钮
       android.R.layout.simple_list_item_checked           //checkbox

    我们可以自定义自己的Layout(list_item.xml):

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"   
        android:textStyle="bold"  
        android:textSize="30sp"  
        android:padding="10sp">  
    </TextView> 

    要注意的是自定义list_item.xml的根节点必须是TextView,否则就会有ArrayAdapter requires the resource ID to be a TextView的错误。
    最后是MainActivity.java代码,先找出ListView,然后往ListView里填充数组data。

    package com.example.hiyou;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.ArrayAdapter;
    import android.widget.ListView;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        /**
         * 初始化组件
         */
        private void initView() {
            String[] data = { "列表1", "列表2", "列表3", "列表4", "列表5" };
            // 绑定XML中的ListView,作为data的容器
            ListView listview = (ListView) findViewById(R.id.list);
            /*
             * 实例化适配器 
             * 第一个参数:Context 
             * 第二个参数:ListView中每一行布局样式 
             * 第三个参数:列表数据容器
             */
            ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,
                    R.layout.list_item, data);
            listview.setAdapter(arrayAdapter);// 将适配器数据映射ListView上
            listview.setOnItemClickListener(new OnItemClickListener() {
    
                @Override
                public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                        long arg3) {
                      Toast.makeText(MainActivity.this,
                                "当前选中列表项的下标为:" + arg2, Toast.LENGTH_SHORT).show();
                }
            });
        }
    
    }
    MainActivity.class

    显示一个带有数据的ListView的步骤如下:
    1.实例一个添加数据的容器,并将数据放入容器。
    2.实例列表适配器,并且实例适配器时将数据传入。
    3.实例一个ListView,并且为其设置适配器。
    4.利用setContentView()函数显示ListView
    因为列表中每一项数据都是一个Item,所以将ListView绑定使用OnItemClickListener项单击监听器,并且重写监听器中的onItemClick()函数。
    onItemClick()函数的第一个参数是出发的适配器,第二个参数数触发的视图,第三个参数是适配器中项的位置下标,第四个参数是ListView项下标。

    2.SimpleAdapter:具有很好扩展性的适配器,可以显示自定义内容。

    SimpleAdapter类官方文档地址:http://developer.android.com/reference/android/widget/SimpleAdapter.html

    修改前面Demo的list_item.xml和MainActivity.class文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
    
        <ImageView
            android:id="@+id/iv"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp" />
    
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
    
            <TextView
                android:id="@+id/bigtv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="3dp"
                android:textSize="20sp" />
    
            <TextView
                android:id="@+id/smalltv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="12sp" />
        </LinearLayout>
    
    </LinearLayout>
    list_item.xml
    package com.example.hiyou;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.ListView;
    import android.widget.SimpleAdapter;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        /**
         * 初始化组件
         */
        private void initView() {
            // 创建动态数组数据源  
            List<HashMap<String, Object>> data = new ArrayList<HashMap<String, Object>>();
            
         // 实例化一个列表数据容器
            HashMap<String, Object> map1 = new HashMap<String, Object>();
            // 往列表容器中添加数据
            /*
             * map.put(String key,Object value) 第一个参数用于初始化适配器时需要映射数据对应的索引;
             * 第二个参数表示对应自定义项布局中的组件数据
             * 进行添加数据时,每一个put()函数都对应自定义ListView项中的一个组件;按钮、复选框等组件是无法映射的。
             */
            map1.put("item1_imageview", R.drawable.list1);
            map1.put("item1_bigtv", "一加手机发布:强调手感 ");
            map1.put("item1_smalltv", "国内手机新品牌一加手机今日在北京发布其首款产品,这是一款强调设计的手机新品,配备骁龙801处理器。16GB版售价1999.99元。");
            // 将列表数据添加到列表容器中
            data.add(map1);
    
            HashMap<String, Object> map2 = new HashMap<String, Object>();
            map2.put("item1_imageview", R.drawable.list2);
            map2.put("item1_bigtv", " LG L90美国发售");
            map2.put("item1_smalltv", "今日,LG L90正式在美国以T-Mobile定制机的形式进行发售,售价为228美元。");
            data.add(map2);
            // 绑定XML中的ListView,作为data的容器
            ListView listview = (ListView) findViewById(R.id.list);
            // 动态数组数据源中与ListItem中每个显示项对应的Key  
            String[] from = new String[] {  "item1_imageview", "item1_bigtv", "item1_smalltv"};  
            // ListItem的XML文件里面的一个ImageView ID和两个TextView ID  
            int[] to = new int[] {  R.id.iv, R.id.bigtv, R.id.smalltv  };  
            // 将动态数组数据源data中的数据填充到ListItem的XML文件list_item.xml中去  
            // 从动态数组数据源data中,取出from数组中key对应的value值,填充到to数组中对应ID的控件中去 
            /*
             * 实例化SimpleAdapter适配器构造函数Simple(Contect context,List data,int resource,String[] from,int[] to)
             *  context:当前context对象
             * data:ListView各项数据 
             * resource:ListView每一项的布局 
             * from:每一项布局中的数据映射索引数组
             * to:每一项中数据对应的组件ID数组
             */
            SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, from, to);
            listview.setAdapter(adapter);// 将适配器数据映射ListView上
            listview.setOnItemClickListener(new OnItemClickListener() {
    
                @Override
                public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                        long arg3) {
                      Toast.makeText(MainActivity.this,
                                "当前选中列表项的为第" + (arg2+1)+"列。", Toast.LENGTH_SHORT).show();
                }
            });
        }
    
    }
    MainActivity.class

    3.SimpleCursorAdapter

    SimpleCursorAdapter类官方文档地址:http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html

     下面用SimpleCursorAdapter来实现上一节中用SimpleAdapter实现的同样的效果,activity_main.xml文件和list_item.xml文件都不需要更改,只需要更改MainActivity.java代码。

    package com.example.hiyou;
    
    import android.app.Activity;
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.ListView;
    import android.widget.SimpleCursorAdapter;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        /**
         * 初始化组件
         */
        private void initView() {
             DBHelper dbHelper = new DBHelper(this);  
                // 向数据库中插入数据  
                insertDataIntoDB(dbHelper);  
                Cursor cursor = dbHelper.query();  
            // 绑定XML中的ListView,作为data的容器
            ListView listview = (ListView) findViewById(R.id.list);
            // 动态数组数据源中与ListItem中每个显示项对应的Key,要与创建的数据库列名一样  
            String[] from = new String[] {  "iv", "bigtv", "smalltv"};  
            // ListItem的XML文件里面的一个ImageView ID和两个TextView ID  
            int[] to = new int[] {  R.id.iv, R.id.bigtv, R.id.smalltv  };  
            // 将动态数组数据源data中的数据填充到ListItem的XML文件list_item.xml中去  
            // 从动态数组数据源data中,取出from数组中key对应的value值,填充到to数组中对应ID的控件中去 
            /*
             * 实例化SimpleCursorAdapter适配器构造函数SimpleCursorAdapter(context, layout, c, from, to)
             *  context:当前context对象
             * layout每一项的布局 
             * c:
             * from:每一项布局中的数据映射索引数组
             * to:每一项中数据对应的组件ID数组
             */
            SimpleCursorAdapter  adapter = new SimpleCursorAdapter (this, R.layout.list_item,cursor, from, to);
            listview.setAdapter(adapter);// 将适配器数据映射ListView上
            listview.setOnItemClickListener(new OnItemClickListener() {
    
                @Override
                public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                        long arg3) {
                      Toast.makeText(MainActivity.this,
                                "当前选中列表项的为第" + (arg2+1)+"列。", Toast.LENGTH_SHORT).show();
                }
            });
        }
    
    
        private void insertDataIntoDB(DBHelper dbHelper) {
            dbHelper.clear();
            //向数据库插入数据
            ContentValues values1 = new ContentValues();
            values1.put("iv", R.drawable.list1);
            values1.put("bigtv", "一加手机发布:强调手感 ");
            values1.put("smalltv",
                    "国内手机新品牌一加手机今日在北京发布其首款产品,这是一款强调设计的手机新品,配备骁龙801处理器。16GB版售价1999.99元。");
            dbHelper.insert(values1);
            ContentValues values2 = new ContentValues();
            values2.put("iv", R.drawable.list2);
            values2.put("bigtv", "LG L90美国发售 ");
            values2.put("smalltv", "今日,LG L90正式在美国以T-Mobile定制机的形式进行发售,售价为228美元。");
            dbHelper.insert(values2);
        }
    }
    MainActivity.class

    这里通过DBHelper这个类来实现数据库的插入和查询功能。

    package com.example.hiyou;
    
    import android.content.ContentValues;
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class DBHelper extends SQLiteOpenHelper {  
          
        public DBHelper(Context context) {  
            super(context, "testDB", null, 1);  
        }  
      
        @Override  
        public void onCreate(SQLiteDatabase db) {  //如果数据库不存在创建数据库tbl_test
            String createTableSQL = "create table IF NOT EXISTS tbl_test "  
                    + "(_id integer primary key autoincrement, iv int, "  
                    + "bigtv text, smalltv text)";  
            db.execSQL(createTableSQL);  
        }  
      //数据新增操作
        public void insert(ContentValues values) {  
            SQLiteDatabase db = getWritableDatabase();  
            db.insert("tbl_test", null, values);  
        }  
      //游标查询数据库
        public Cursor query() {  
            SQLiteDatabase db = getWritableDatabase();  
            Cursor cursor = db.query("tbl_test", null, null, null, null, null, null);  
            return cursor;  
        }  
      //清除数据库中的数据
        public void clear() {  
            SQLiteDatabase db = getWritableDatabase();  
            db.delete("tbl_test", null, null);  
        }  
      //关闭读取数据库
        public void close() {  
            SQLiteDatabase db = getWritableDatabase();  
            db.close();  
        }  
      
        @Override  
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
        }  
      
    }  
    DBHelper.class

    自定义Adapter

    使用android提供的adapter来绘制列表的话,列表的每一项的显示都是一样的。而且按钮和复选框等这些事件的组件其实是无法将数据映射在ListView上的。所以如果要监听和响应按钮、复选框等组件的事件时,则需要进行自定义适配器来完成。

    下面示例实现获取SD卡内的MP3格式歌曲信息通过ListView显示歌曲专辑图片、歌曲名称、歌手名,并且ListView的单双行不同颜色显示,这需要自定义adapter的子类。adapter的常用子类有BaseAdapter、ArrayAdapter、SimpleAdapter等,下面介绍自定义BaseAdapter和ArrayAdapter的实现。

    1.自定义BaseAdapter

    为了实现ListView的单双行不同颜色显示,需要自定义adapter的子类,下面我们实现自定义的MusicAdapter类。MusicAdapter类继承自BaseAdapter类,BaseAdapter为抽象类,继承它需要实现如下方法,因此具有较高的灵活性。

    public class MusicAdapter extends BaseAdapter {  
      
        @Override  
        public int getCount() {  
            return 0;  
        }  
      
        @Override  
        public Object getItem(int arg0) {  
            return null;  
        }  
      
        @Override  
        public long getItemId(int position) {  
            return 0;  
        }  
      
      //实例化布局和组件以及设置组件数据
      //getView(int position, View convertView, ViewGroup parent) 
      //position:绘制的行数
      //convertView:绘制的视图,这里指的是ListView中的每一项布局
      //parent:view的合集
        @Override  
        public View getView(int position, View convertView, ViewGroup parent) {  
            // TODO Auto-generated method stub  
            return null;  
        }  
    }  

    ListView在绘制时首先会调用getCount()方法得到绘制次数,然后通过getView()方法一层一层进行绘制,所以我们可以在getView()方法中根据position(当前绘制的ID)来的修改绘制内容。而getItem()和getItemId()则在需要处理和取得Adapter中的数据时调用。

    package com.example.hiyou;
    
    import java.util.ArrayList;
    import java.util.List;
    import android.content.ContentResolver;
    import android.content.Context;
    import android.database.Cursor;
    import android.graphics.Bitmap;
    import android.provider.MediaStore;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    public class MusicAdapter  extends BaseAdapter {  
          private int[] colors = new int[] { 0xff3cb371, 0xffa0a0a0 };  
         // 用来获得ContentProvider(共享数据库)
        public ContentResolver cr;
        // 用来装查询到的音乐文件数据
        public Cursor cur;
        // 歌曲信息列表
        public List<MusicInfo> musicList;
        public Context context;
    
        public MusicAdapter(Context context) {
            this.context = context;
            // 取得数据库对象
            cr = context.getContentResolver();
            musicList = new ArrayList<MusicInfo>();
    
            String[] mString = new String[] {  MediaStore.Audio.Media.DISPLAY_NAME,
                    MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST,
                    MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.SIZE,
                    MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.DATA,MediaStore.Audio.Media._ID };
            // 查询所有音乐信息
            cur = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mString,
                    null, null, null);
    
            if (cur != null) {
                // 移动游标到第一个
                cur.moveToFirst();
                int j = 1;
                for (int i = 0; i < cur.getCount(); i++) {
                    if (cur.getString(0).endsWith(".mp3")) {// 过滤获取MP3文件
                        MusicInfo mInfo = new MusicInfo();
                        String musicName = cur.getString(0).substring(0,
                                cur.getString(0).lastIndexOf(".mp3"));
                        mInfo.setMusicIndex(j++);
                        mInfo.setMusicName(musicName);
                        mInfo.setMusicAlubm(cur.getString(1));
                        mInfo.setMusicSinger(cur.getString(2));
                        mInfo.setMusicTime(cur.getInt(3));
                        mInfo.setMusicSize(cur.getInt(4));
                        mInfo.setMusicAlubmId(cur.getInt(5));
                        mInfo.setMusicPath(cur.getString(6));
                        mInfo.setMusicId(cur.getInt(7));
                        musicList.add(mInfo);
                    }
                    cur.moveToNext();
                }
    
            }
        }
    
        @Override
        public int getCount() {
            return musicList.size();//返回ListView项的长度
        }
    
        @Override
        public Object getItem(int arg0) {
            return musicList.get(arg0);
        }
    
        @Override
        public long getItemId(int arg0) {
            return arg0;
        }
    
      //实例化布局和组件以及设置组件数据
      //getView(int position, View convertView, ViewGroup parent) 
      //position:绘制的行数
      //convertView:绘制的视图,这里指的是ListView中的每一项布局
      //parent:view的合集
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;  
             if (convertView == null) {  
                 holder = new ViewHolder();  
                 //将布局通过LayoutInflater对象实例化为一个view
                 convertView = LayoutInflater.from(context).inflate(  
                         R.layout.list_item, null);  
                 holder.songImage = (ImageView) convertView.findViewById(R.id.listImage);  
                 holder.singerName = (TextView) convertView.findViewById(R.id.list_Singer);  
                 holder.songName = (TextView) convertView.findViewById(R.id.listName);  
              // 将holder绑定到convertView  
                 convertView.setTag(holder); 
             }else {  
                 holder = (ViewHolder) convertView.getTag();  
             }  
             // 向ViewHolder中填入的数据  
            int mid = musicList.get(position).getMusicIndex();
            String musicName = musicList.get(position).getMusicName();
            String musciSinger = musicList.get(position).getMusicSinger();
            if (musciSinger.contains("<unknown>")) {
                musciSinger = "<未知>";
            }
            Bitmap img = MusicUtils.getArtwork(context,musicList.get(position).getMusicId(),musicList.get(position).getMusicAlubmId(), true);
            holder.songName.setText(mid + ". " + musicName);
            holder.singerName.setText(musciSinger);
            holder.songImage.setImageBitmap(img);
            int colorPos = position % colors.length;  
            convertView.setBackgroundColor(colors[colorPos]);  //控制背景颜色
            return convertView;
        }
    
        /** 
         * ViewHolder类用以储存item中控件的引用 
         */  
        final class ViewHolder {  
            ImageView songImage;  
            TextView songName;  
            TextView singerName;  
        }  
    
    }  
    
    MusicAdapter.class
    MusicAdapter

    getView()方法用来获得绘制每个item的View对象,如果每次getView()被执行都new出一个View对象,长此以往会产生很大的消耗,特别当item中还有Bitmap等,甚至会造成OOM的错误导致程序崩溃。从上面的代码可以看到getView()有一个convertView参数,这个参数用来缓存View对象。当ListView滑动的过程中,会有item被滑出屏幕而不再被使用,这时候Android会回收这个item的view,这个view也就是这里的convertView。这样如果convertView不为null,就不用new出一个新的View对象,只用往convertView中填充新的item,这样就省去了new View的大量开销。

    在上面的代码中,在缓存convertView减少new View开销的同时,通过setTag()方法将数据结构ViewHolder绑定到convertView,从而利用ViewHolder存储convertView中控件对象的引用,这样避免每次调用findViewById()方法。

    相关类:

    package com.example.hiyou;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.ListView;
    
    public class MainActivity extends Activity {
    
        public MusicAdapter mAdapter;
        private ListView mListView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        /**
         * 初始化组件
         */
        private void initView() {
    
            // 绑定XML中的ListView,作为Item的容器
            mListView = (ListView) findViewById(R.id.list);
            mAdapter = new MusicAdapter(MainActivity.this);
            mListView.setAdapter(mAdapter);
        }
    
    }
    MainActivity.class
    package com.example.hiyou;
    
    import java.io.FileDescriptor;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import android.content.ContentResolver;
    import android.content.ContentUris;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.net.Uri;
    import android.os.ParcelFileDescriptor;
    /***
     * 
     * @author Jerryc
     *音乐助手类
     */
    public class MusicUtils {
        private static final Uri sArtworkUri = Uri
                .parse("content://media/external/audio/albumart");
        private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
        private static Bitmap mCachedBit = null;
    //获取音乐文件专辑图片
        public static Bitmap getArtwork(Context context, long song_id,
                long album_id, boolean allowdefault) {
            if (album_id < 0) {
                // This is something that is not in the database, so get the album
                // art directly
                // from the file.
                if (song_id >= 0) {
                    Bitmap bm = getArtworkFromFile(context, song_id, -1);
                    if (bm != null) {
                        return bm;
                    }
                }
                if (allowdefault) {
                    return getDefaultArtwork(context);
                }
                return null;
            }
            ContentResolver res = context.getContentResolver();
            Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
            if (uri != null) {
                InputStream in = null;
                try {
                    in = res.openInputStream(uri);
                    return BitmapFactory.decodeStream(in, null, sBitmapOptions);
                } catch (FileNotFoundException ex) {
                    // The album art thumbnail does not actually exist. Maybe the
                    // user deleted it, or
                    // maybe it never existed to begin with.
                    Bitmap bm = getArtworkFromFile(context, song_id, album_id);
                    if (bm != null) {
                        if (bm.getConfig() == null) {
                            bm = bm.copy(Bitmap.Config.RGB_565, false);
                            if (bm == null && allowdefault) {
                                return getDefaultArtwork(context);
                            }
                        }
                    } else if (allowdefault) {
                        bm = getDefaultArtwork(context);
                    }
                    return bm;
                } finally {
                    try {
                        if (in != null) {
                            in.close();
                        }
                    } catch (IOException ex) {
                    }
                }
            }
    
            return null;
        }
    
        private static Bitmap getArtworkFromFile(Context context, long songid,
                long albumid) {
            Bitmap bm = null;
            byte[] art = null;
            String path = null;
            if (albumid < 0 && songid < 0) {
                throw new IllegalArgumentException(
                        "Must specify an album or a song id");
            }
            try {
                if (albumid < 0) {
                    Uri uri = Uri.parse("content://media/external/audio/media/"
                            + songid + "/albumart");
                    ParcelFileDescriptor pfd = context.getContentResolver()
                            .openFileDescriptor(uri, "r");
                    if (pfd != null) {
                        FileDescriptor fd = pfd.getFileDescriptor();
                        bm = BitmapFactory.decodeFileDescriptor(fd);
                    }
                } else {
                    Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid);
                    ParcelFileDescriptor pfd = context.getContentResolver()
                            .openFileDescriptor(uri, "r");
                    if (pfd != null) {
                        FileDescriptor fd = pfd.getFileDescriptor();
                        bm = BitmapFactory.decodeFileDescriptor(fd);
                    }
                }
            } catch (FileNotFoundException ex) {
    
            }
            if (bm != null) {
                mCachedBit = bm;
            }
            return bm;
        }
    
        private static Bitmap getDefaultArtwork(Context context) {
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inPreferredConfig = Bitmap.Config.RGB_565;
            return BitmapFactory.decodeStream(context.getResources()
                    .openRawResource(R.drawable.album), null, opts);
        }
    }
    MusicUtils.class
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
    
        <LinearLayout
            android:layout_width="50sp"
            android:layout_height="50sp"
            android:orientation="vertical" android:gravity="center" >
    
            <ImageView
                android:id="@+id/listImage"
                android:layout_width="40sp"
                android:layout_height="40sp" />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50sp"
            android:orientation="vertical" >
    
            <TextView
                android:id="@+id/listName"
                android:layout_width="fill_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center_vertical"
                android:paddingLeft="10dp"
                android:singleLine="true"
                android:textSize="16sp" />
    
            <TextView
                android:id="@+id/list_Singer"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center_vertical"
                android:paddingLeft="10dp"
                android:singleLine="true"
                android:textSize="13sp" />
        </LinearLayout>
    
    </LinearLayout>
    list_item.xml
    package com.example.hiyou;
    
    /**
     *   歌曲信息类
     */
    public class MusicInfo {
        private int musicIndex;  //排序号
        private int songId;//歌曲ID
        private int musicAlubmId;//专辑ID
        private String musicName;// 歌曲名
        private String musicSinger;// 歌手名
        private int musicTime;// 歌曲时间长度
        private String musicAlubm;// 专辑名称
        private int musicSize;// 曲歌大小
        private String musicPath;// 歌曲路径
    
        public int getMusicIndex() {
            return musicIndex;
        }
    
        public void setMusicIndex(int musicIndex) {
            this.musicIndex = musicIndex;
        }
        public int getMusicId() {
            return songId;
        }
    
        public void setMusicId(int songId) {
            this.songId = songId;
        }
        public int getMusicAlubmId() {
            return musicAlubmId;
        }
    
        public void setMusicAlubmId(int musicAlubmId) {
            this.musicAlubmId = musicAlubmId;
        }
    
        public String getMusicName() {
            return musicName;
        }
    
        public void setMusicName(String musicName) {
            this.musicName = musicName;
        }
    
        public String getMusicSinger() {
            return musicSinger;
        }
    
        public void setMusicSinger(String musicSinger) {
            this.musicSinger = musicSinger;
        }
    
        public int getMusicTime() {
            return musicTime;
        }
    
        public void setMusicTime(int musicTime) {
            this.musicTime = musicTime;
        }
    
        public String getMusicAlubm() {
            return musicAlubm;
        }
    
        public void setMusicAlubm(String musicAlubm) {
            this.musicAlubm = musicAlubm;
        }
    
        public int getMusicSize() {
            return musicSize;
        }
    
        public void setMusicSize(int musicSize) {
            this.musicSize = musicSize;
        }
    
        public String getMusicPath() {
            return musicPath;
        }
    
        public void setMusicPath(String musicPath) {
            this.musicPath = musicPath;
        }
    
    }
    MusicInfo.class

    2.自定义ArrayAdapter<T>

    在开发中需要将对象显示在listview中,这时候使用ArrayAdapter<T>来显示指定对象类型。下面自定义ArrayAdapter<T>实现上一节中自定义BaseAdapter实现的同样的效果,首先定义要显示的对象,代码参照前面的MusicInfo.class

    MainActivity.java代码修改如下:

    package com.example.hiyou;
    
    import java.util.ArrayList;
    import android.app.Activity;
    import android.content.ContentResolver;
    import android.database.Cursor;
    import android.os.Bundle;
    import android.provider.MediaStore;
    import android.widget.ListView;
    
    public class MainActivity extends Activity {
    
        public MyArrayAdapter mAdapter;
        private ListView mListView;
        // 用来获得ContentProvider(共享数据库)
        public ContentResolver cr;
        // 用来装查询到的音乐文件数据
        public Cursor cur;
        // 歌曲信息列表
        public ArrayList<MusicInfo> musicList;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        /**
         * 初始化组件
         */
        private void initView() {
            // 取得数据库对象
            cr = getContentResolver();
            musicList = new ArrayList<MusicInfo>();
            String[] mString = new String[] { MediaStore.Audio.Media.DISPLAY_NAME,
                    MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST,
                    MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.SIZE,
                    MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.DATA,
                    MediaStore.Audio.Media._ID };
            // 查询所有音乐信息
            cur = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mString,
                    null, null, null);
    
            if (cur != null) {
                // 移动游标到第一个
                cur.moveToFirst();
                int j = 1;
                for (int i = 0; i < cur.getCount(); i++) {
                    if (cur.getString(0).endsWith(".mp3")) {// 过滤获取MP3文件
                        MusicInfo mInfo = new MusicInfo();
                        String musicName = cur.getString(0).substring(0,
                                cur.getString(0).lastIndexOf(".mp3"));
                        mInfo.setMusicIndex(j++);
                        mInfo.setMusicName(musicName);
                        mInfo.setMusicAlubm(cur.getString(1));
                        mInfo.setMusicSinger(cur.getString(2));
                        mInfo.setMusicTime(cur.getInt(3));
                        mInfo.setMusicSize(cur.getInt(4));
                        mInfo.setMusicAlubmId(cur.getInt(5));
                        mInfo.setMusicPath(cur.getString(6));
                        mInfo.setMusicId(cur.getInt(7));
                        musicList.add(mInfo);
                    }
                    cur.moveToNext();
                }
    
            }
    
            // 绑定XML中的ListView,作为Item的容器
            mListView = (ListView) findViewById(R.id.list);
            mAdapter = new MyArrayAdapter(MainActivity.this, R.layout.list_item,
                    musicList);
            mListView.setAdapter(mAdapter);
        }
    
    }
    MainActivity

    接下来自定义继承自ArrayAdapter<MusicInfo>MyArrayAdapter类,继承ArrayAdapter<MusicInfo>只需要重写getView()方法就可以实现与上一节相同的效果,并且不用保存List<MusicInfo>对象引用。

    package com.example.hiyou;
    
    import java.util.List;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    public class MyArrayAdapter extends ArrayAdapter<MusicInfo> {
         private int[] colors = new int[] { 0xff3cb371, 0xffa0a0a0 };
        private Context mContext;
        private int resource;
    
        public MyArrayAdapter(Context context, int resource,List<MusicInfo> musicList) {
            super(context, resource,musicList);
            this.mContext = context;
            this.resource = resource;
            
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;  
            if (convertView == null) {  
                holder = new ViewHolder();  
                convertView = LayoutInflater.from(mContext).inflate(  
                       resource, null);  
                holder.songImage = (ImageView) convertView.findViewById(R.id.listImage);  
                holder.singerName = (TextView) convertView.findViewById(R.id.list_Singer);  
                holder.songName = (TextView) convertView.findViewById(R.id.listName);  
             // 将holder绑定到convertView  
                convertView.setTag(holder); 
            }else {  
                holder = (ViewHolder) convertView.getTag();  
            }  
            // 向ViewHolder中填入的数据  
           int mid =getItem(position).getMusicIndex();
           String musicName = getItem(position).getMusicName();
           String musciSinger =getItem(position).getMusicSinger();
           if (musciSinger.contains("<unknown>")) {
               musciSinger = "<未知>";
           }
           Bitmap img = MusicUtils.getArtwork(mContext,getItem(position).getMusicId(),getItem(position).getMusicAlubmId(), true);
           holder.songName.setText(mid + ". " + musicName);
           holder.singerName.setText(musciSinger);
           holder.songImage.setImageBitmap(img);
           int colorPos = position % colors.length;  
           convertView.setBackgroundColor(colors[colorPos]);  //控制背景颜色
           return convertView;
       }
    
       /** 
        * ViewHolder类用以储存item中控件的引用 
        */  
       final class ViewHolder {  
           ImageView songImage;  
           TextView songName;  
           TextView singerName;  
       }  
    
    }
    MyArrayAdapter
  • 相关阅读:
    window.onload和$(document).ready(function(){})的区别
    javascript:让表单 文本框 只读,不可编辑的方法
    JQuery操作下拉框
    项目中常用js方法整理common.js
    embed标签动态改变Src的值,局部刷新播放其他视频的javascript方法
    在BootStrap的modal中使用Select2
    小数点校验,只允许输入数字,小时点后只有两位
    带左右按钮的左右循环滚动轮播图,图片上带css3动效,用于显示文字
    JavaScript 函数方法
    JavaScript 函数方法
  • 原文地址:https://www.cnblogs.com/davidyc/p/3679514.html
Copyright © 2011-2022 走看看