读《50 Android Hacks》笔记整理Hack 24~Hack 30

第六章 活用列表和适配器

Hack 24 处理空列表

ListView以及其它继承自AdapterView的类可以通过setEmptyView(View)方法处理空状态,当需要绘制AdapterView时,如果适配器为null或适配器的isEmpty()方法返回true,此时会显示setEmptyView(View)方法所设置的视图。
假设:需要创建一个应用程序处理TODO列表,主界面是一个显示所有TODO项的ListView,但当第一次启动该应用程序时,列表是空的。对于这种空状态,我们可以在界面上绘制一张图片。布局文件代码如下:

<FrameLayout
    ...>
<ListView 
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

<ImageView 
    android:id="@+id/empty_view"
    android:layout_width="match_parent"
    android:layout_height"match_parent"
    android:src="@drawable/empty_view"/>
</FrameLayout>

Activity中实现,代码如下:

public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    ListView mListView = (ListView)findViewById(R.id.list_view);
    mListView.setEmptyView(findViewById(R.id.empty_view));
}

这里我们为了测试,所以没有为ListView设置适配器,运行代码就会显示imageview。
我吗也可以尝试使用ViewStub作为空状态时显示的视图。该方法可以保证在不需要显示该视图时,不必填充(inflate)该视图。

外链地址1


Hack 25 通过ViewHolder优化适配器

Adapter开发文档介绍:
“Adapter对象是AdapterView和底层数据间的桥梁。Adapter用于访问数据项,并且负责为数据项生成视图。“
AdapterView是一个抽象类,用于那些需要通过Adapter填充自身的视图。常见子类是ListView。显示AdapterView时,会调用Adapter的getView()方法创建并添加每个子条目的视图。Adapter的getView()方法就是用来创建这些视图的。Adapter并不会为每行数据都创建一个新视图,而是提供了回收视图的方法。
运行机制:
当getView()方法被调用时,如果convertView参数不为null,就使用convertView,不用在新建视图。我们需要通过convertView.findViewById()方法获取每个ui控件的引用然后使用与当前位置绑定的数据来填充视图。
这里我们可以使用ViewHolder模式,ViewHolder是一个静态类,可以用于保存每行视图以避免每次调用getView()时都会调用findViewById()。

外链地址1
外链地址2


Hack 26 为ListView添加分段标头

分段标头就像是有的手机中通讯录的样式,根据姓名首字母进行分组,分段标头一直显示在屏幕顶端。
实现方式:
一。
开发者实现这个需求通常是创建两种类型的列表:
1.常规列表用于显示数据
2.特殊列表用于显示分段标头
这种方式需要重写getViewTypeCount()方法,让其返回2;然后修改getView()方法,在该方法中创建并返回对应类型的列表项。
缺点:会导致代码逻辑混乱。
二。
可以在列表项中嵌入分段标头,然后根据需求显示或隐藏分段标头。我们可以创建一个特殊的TextView,让其叠加在列表的顶部,当列表滚动到一个新的分段时,就更新其内容。
优点:简化了创建列表以及选项列表项的逻辑。

26.1 创建列表布局

我们先在单独文件中为分段标头创建布局,这样就可以在随着列表滚动的分段标头和列表顶部的固定分段标头中复用这个布局文件。代码如下:

<TextView
    ...
    android:id="@+id/header"
    android:background="#0000ff"
    style="@android:style/TextAppearance.Small"/>

包含固定分段标头的XML布局文件,代码如下:

<FrameLayout ...>
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
        <include layout="@layout/header"/>
</FrameLayout>

列表使用Android标准的列表ID,因此可以在ListActivity的子类中使用它。将分段标头包含在帧布局中,这样标头就可以与列表重叠在一起了。
创建列表的布局文件,代码如下:

<LinearLayout ...>
    <include layout="@layout/header"/>
    <TextView
        android:id="@+id/label"
        style=""@android:style/TextAppearance.Large
        android:layout_width="mathc_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

26.2 创建可视分段标头

与其它创建分段列表的方式不同之处在于:开发者只需要重写getView()方法。我们不需要返回多种类型的视图,也不需要在分段列表与原始列表间转换数据项的位置(position)。代码如下:

public class SectionAdapter extends ArrayAdapter<String>{
    private Activity activity;
    public SectionAdapter(Activity activity,String[] objects){
        //1.为自定义视图指定xml布局文件
        super(activity,R.layout.list_item,R.id.label.bjects);
        this.activity = activity;
    }
    @Override
    public View getView(int position,View view,ViewGroup parent){
    if(view == null){
        view = activity.getLayoutInflater().inflate(R.layout.list_item,parent,false);
    }   
    TextView header = (TextView) view.findViewById(R.id.header);
    String label = getItem(position);
    //2.检查列表顶部起始字母是否发生改变
    if(position == 0 || getItem(position - 1).charAt(0) != label.charAt(0)){
        //3.显示分段标头,并更改分段标头的文本内容
        header.setVisibility(View.VISIBLE);
        header.setText(label.substring(0,1));
    }else{
        //4.隐藏分段标头
        header.setVisibility(View.GONE);
    }
    return super.getView(position,view,parent);
    }
}

用于配置屏幕顶部悬浮分段标头辅助方法,代码如下:

//1.用于访问分段标头
private TextView topHeader;
...
private void setTopHeader(int pos){
final String text = Countries.COUNTRIES[pos].substring(0,1);
//2.更新文本内容
topHeader.setText(text);
}

26.3 最后一步

我们在Activity的onCreate()方法中国年整合所有内容。代码如下:

private int topVisiblePosition;
...
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.list);
    topHeader = (TextView)findViewById(R.id.top);
    setListAdapter(new SectionAdapter(this,Countries.COUNTRIES));
    //1.设置滚动监听器
    getListView().setOnScrollListener(new  AbsListView.OnScrollListener(){
        @Override
        public void onScrollStateChanged(AbsListView view,int scrollState){
            //Empty
        }
        @Override
        public void onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount){
            if(firstVisibleItem != topVisiblePosition){
                topVisiblePosition = firstVisibleItem;
                //2.调用辅助方法
                setTopHeader(firstVisibleItem);
            }
        }
    });
    //3.初始化第一个列表项的分段标头
    setTopHeader(0);
}

外链地址1
外链地址2


Hack 27 使用Activity和Delegate与适配器交互

委托模式(Delegate Pattern)在iOS开发中被大量使用。如:创建http请求,开发者可以设置一个委托对象,当请求处理完毕后指定一些操作。
在这里开发者可以使用关注点分离(separation哦分concerns,SoC)的设计原则。
委托模式:可以帮助开发者把所有业务逻辑从适配器中移到Activity中。
实现思路:
如:我们在适配器中实现删除按钮点击处理器,但并不在适配器中实现删除对象的方法。我们通过一个委托接口调用Activity的方法删除对象。代码如下:

public class NumbersAdapter extends ArrayAdapter<Integer>{
    //1.定义委托接口
    public static interface NumbersAdapterDelegate{
        void removeItem(Integer value);
    }
    private LayoutInflater mInflator;
    private NumbersAdapterDelegate mDelegate;
    public NumbersAdapter(Context context,List<Integer> objects){
        super(context,0,objects);
        mInflator = LayoutInflater.from(context);
    }
    @Override
    public view getView(int position,View cv,ViewGroup parent){
        if(null == cv){
            cv = mInflator.inflate(R.layout.number_row,parent,false);
        }
        final Integer value = getItem(position);
        TextView tv = (TextView)cv.findViewById(R.id.number_row_text);
        tv.setText(value.toString());
        View button = cv.findViewById(R.id.numbers_row_button);
        button.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v){
                if(null != mDelegate){
                    //2.删除对象
                    mDelegate.removeItem(value);
                }
            }
        });
        return cv;
    }
    //3.为适配器设置委托对象
    public void setDelegate(NumbersAdapterDelegate delegate){
        mDelegate = delegate;
    }
}

适配器准备就绪,Activity的代码如下:

//1.实现NumberAdapterDelegate接口
public class MainActivity extends Activity implements NumbersAdapterDelegate{
    private ListView mListView;
    private ArrayList<Integer> mNumbers;
    private NumbersAdapter mAdapter;
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mListView = () findViewById(R.id.main_listview);
        mNumbers = new ArrayList<Integer>();
        mAdapter = new NumbersAdapter(this,mNumbers);
        mListView.setAdapter(mAdapter);
    }
    @Override
    protected void onResume(){
        super.onResume();
        //2.在onResume()方法中注册委托对象
        mAdapter.setDelegate(this);
    }
    @Override
    protected void onPause(){
        super.onPause();
        //3.在onPause()方法中取消注册委托对象
        mAdapter.setDelegate(null);
    }
    @Override
    public void removeItem(Integer value){
        //从列表中移除指定项,然后通知适配器绑定的数据发生变化
        mNumbers.remove(value);
        mAdapter.notifyDataSetChanged();
    }
}

我们在onCreate()方法中将当前Activity设置为适配器的委托对象,而是在onResume()方法中注册代理对象,然后在onPause()方法中取消注册。这样做的目的是为了确保只在Activity显示在屏幕上的时候才作为委托对象使用。

外链地址1
外链地址2


Hack 28 充分利用ListView的头视图

需求:在界面上提供一个显示图片的相册和一个显示数字的列表,当向下滚动界面时,相册也会随之滚动,直到图片消失。
一般想法:把GalleryListView这个两个控件置于ScrollView中。
但这样是不行的,因为ListView本身就是一种ScrollView,会出现滚动事件的冲突。
可以使用的做法:
ListView提供了可以为列表添加头视图(Header Veiw)和尾视图(Footer View)的方法。
使用如上方法把Gallery设置为ListView的代码如下:

public class MainActivity extends Activity{
    private static final String[] NUMBERS = {"1","2","3","4","5"};
    private Gallery mGallery;
    private View mHeader;
    private ListView mListView;

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //1.获取ListView的引用
        mListView = (ListView) findViewById(R.id.main_listview);
        //2.创建需要被填充的xml文件
        LayoutInflater inflator = LayoutInflater.from(this);
        mHeader = inflator.inflate(R.layout.header,mListView,false);
        mGallery = (Gallery) mHeader.findViewById(R.id.gallery);
        mGallery.setAdapter(new ImageAdapter(this));

        //3.替换视图的原始LayoutParams
        ListView.LayoutParams params = new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,ListView.LayoutParams.WRAP_CONTENT);
        mHeader.setLayoutParams(params);
        //4.将这个头视图添加到ListView中
        mListView.addHeaderView(mHeader,null,false);
        //5.为ListView设置适配器
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.list_item,NUMBERS);
        mListView.setAdapter(adapter);
        //6.添加一个onItemClick监视器
        mListView.setOnItemClickListener(new OnItemClickListener(){
            @Override
            public void onItemClick(AdapterView<?> parent,View view,int position,long id){
                mGallery.setSelection(position-1);
            }
        });

    }
}

我们可以通过监听器实现点击列表中某个数字时,滚动到相册中对应图片。

外链地址1
外链地址2


Hack 29 在ViewPager中处理转屏

ViewPager可以用于创建任何需要显示分页视图的应用程序,用法与AdapterView相似。
假设需要创建一个电子杂志风格的app,要在不用分页中分别采取不同横竖屏显示。
需要的组件:
Activity:持有ViewPager的引用、控制屏幕旋转
ColorFragment类:用于显示颜色,并在屏幕中央显示文本内容
ColorAdapter类:负责创建Fragment、通知Activity对于哪个Fragment需要改变屏幕显示方向
ViewPager:使用ColorAdapter显示Fragment
代码如下:

public class MainActivity extends FragmentActivity{
    private ViewPager mViewPager;
    private ColorAdapter mAdapter;
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        //1.设置默认屏幕方向
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        setContentView(R.layout.main);
        //2.引用viewpager
        mViewPager = (ViewPager) findViewById();
        mAdapter = new ColorAdapter();
        mViewPager.setAdapter();
        //3.添加监听器
        mViewPager.setOnPageChangeListener(new OnPageChangeListener(){
            @Override
            public void onPageSelected(){
                if(mAdapter.usesLandscape(position)){
                    allowOrientationChanges();
                }else{
                    enforcePortrait();
                }
            }
            ...
        });
    }
    //4.实现方法
    public void allowOrientationChanges(){
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
    } 

    public void enforcePortrait(){
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }
}

第一步是将默认屏幕方向设置为竖屏,如果视图没有指定是否需要改变屏幕方向,就竖屏显示该视图。代码中持有ViewPager的引用,我们为其设置ColorAdapter,此外还需添加一个监听器用于监听页面切换。该监听器会通过适配器判断是否需要改变屏幕方向,最后调用Activity类提供的setRequestedOrientation()方法改变屏幕方向。
ViewPager类是Android用于水平视图切换的标准实现,可以向后兼容到API level 4。我吗最好每个视图都可以支持两种不同屏幕方向,当用户使用应用程序的时候,如果可以改变屏幕方向,会提升用户体验。

外链地址1
外链地址2


Hack 30 ListView的选择模式

ListView定义了choiceMode属性,开发文档如下:
“用于为视图定义选择行为。默认情况下,列表是没有任何选择行为的。如果把choiceMode设置为singleChoice,列表允许又一个列表项处于被选择状态。如果把choiceMode设置为multipleChoice,那么列表允许有任意数量的列表项处于被选择状态。“
ListView另一个有趣的功能是:不管使用singleChoice还是multipleChoice,所选列表项的位置信息都会被自动保存。
示例代码如下:

<LinearLayout ...>
    <Button
        android:onClick="onPickCountryClick"
        .../>
    <ListView
        android:choiceMode="singleChoice"
        .../>
</LinearLayout>

使用按钮来执行指定方法,我们使用选择模式让ListView显示列表。
Activity代码如下:

public class MainActivity extends Activity{
    private ListView mListView;
    private CountryAdapter mAdapter;
    private List<Country> mCountries;
    private String mToastFmt;
    @Override
    public void onCreate(...){
        super.onCreate(...);
        setContentView(R.layout.activity_main);
        //向列表中填充信息辅助方法
        createCountriesList();
        mToastFmt = getString(R.string.activity_main_toast_fmt);
        mAdapter = new CountryAdapter(this,-1,mCountries);
        mListView = (ListView) findViewById(R.id.activity_main_list);
        mListVeiw.setAdapter(mAdapter);
    }
    public void onPickCountryClick(View v){
        int pos = mListView.getCheckedItemPosition();
        if(ListView.INVALID_POSITION != pos){
            String msg = String.format(mToastFmt,mCountries.get(pos).getName());
            Toast.makeText(this,msg,Toasst.LENGTH_SHORT).show();
        }
    }
}

布局文件代码如下:

<LinearLayout ...>
    <TextView
        android:id="@+id/country_view_title"
        .../>
    <CheckBox
        android:id="@+id/country_view_checkbox"
        .../>
</LinearLayout>

ListView是以某种方式通过Checkable接口来处理视图的选择状态,在ListView源码中有如下代码:

if(mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null){
    if(child instanceof Checkable){
        ((Checkable)child).setChecked(mCheckStates.get(position));
    }
}

所以如果需要ListView处理选择行为,需要令列表项对应的自定义视图实现Checkable接口。遗憾的是,这种方式必须创建自定义视图。
我们创建的CountryView类代码如下:

public class CountryView extends LinearLayout implements Checkable{
    private TextView mTitle;
    private CheckBox mCheckBox;
    public CountryView(Context context,AttributeSet attrs){
        super(context,attrs);
        //填充布局
        LayoutInflater inflater = LayoutInflater.from(context);
        View v = inflater.inflate(R.layout.country_view,this,true);
        mTitle = (TextView) v.findViewById(R.id.country_view_title);
        mCheckBox = (CheckBox) v.findViewById(R.id.country_view_checkbox);
    }
    public void setTitle(String title){
        mTitle.setText(title);
    }
    @Override
    public boolean isChecked(){
        //重写所有Checkable接口方法
        return mCheckBox.isChecked();
    }
    @Override
    public void setChecked(boolean checked){
        mCheckBox.setChecked(checked);
    }
    @Override
    public void toggle(){
        mCheckBox.toggle();
    }
}

这里每个被实现的接口方法都调用了mCheckBox的相应方法。这意味着,在ListView中选择一行时,会调用CountryView的setChecked()方法。
这时我们点击某行时CheckBox中没有打勾,但点击CheckBox时,CheckBox就会打勾,这是哪里出问题了呢?
问题出现在我们添加了一个可获取焦点的ui控件CheckBox。解决方法就是设置CheckBox不能点击。

android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"

外链地址1
外链地址2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Debug hacks指的是在软件或应用程序开发的过程中,使用不正规方法来定位和修正程序的某些问题或缺陷。通常,这些方法会利用一些不受支持的程序或工具,或修改已存在的代码,以使程序在调试和测试期间更容易诊断问题。 Debug hacks的使用在软件开发中非常普遍,特别是在开发软件时,时间和资源很紧缺的情况下。但是,这些方法可能会损害程序的安全性、可靠性和稳定性。因此,除非情况紧急或必须,一般不建议使用Debug hacks方法。 Debug hacks的一些常见技巧包括调试器、反汇编、内存操作、代码注入、代码覆盖和补丁等。而使用Debug hacks的主要风险包括: 1. 不可预知的问题:由于不受支持的方法,程序可能会出现意外的行为,导致开发者难以处理。 2. 安全问题:由于Debug hacks方法可能会在程序中注入代码,因此,黑客可以利用这些注入代码来攻击程序或系统。 3. 兼容性问题:由于Debug hacks方法往往依赖于特定的程序版本或操作系统,因此可能在不同的环境下出现兼容性问题。 因此,使用Debug hacks时,必须谨慎,并确保只在必要情况下使用。在可能的情况下,应该使用正规的调试和测试方法来诊断和修复程序问题。 ### 回答2: debug hacks是一种程序员常用的调试技巧,通过在代码中插入一些调试代码来定位程序中的错误并修复它们。通常,调试代码会在程序执行时输出某些信息,例如变量的值、函数的返回值或程序运行流程。这些输出信息可以帮助程序员找到程序中的错误并解决它们。 使用debug hacks的过程中,程序员需要仔细考虑调试代码的位置和内容,以避免引入新的错误或影响程序的性能。另外,调试代码在定位错误后必须及时删除,以防止对程序的影响。 常用的debug hacks包括: 1. 打印调试信息:在代码中插入打印语句,输出变量的值、函数的返回值或程序运行流程等信息。 2. 调试断点:在代码中设置调试断点,当程序执行到断点处时,程序会停止执行,程序员可以在此处检查代码并逐步执行程序。 3. 远程调试:通过网络连接到远程计算机,在另一台计算机上调试程序。 4. 内存调试:使用特定的工具检查程序的内存使用情况,找出内存泄漏和其他内存相关问题。 综上所述,debug hacks是程序员调试程序时常用的技巧,它可以帮助程序员定位程序中隐藏的错误并修复它们。这需要程序员仔细考虑调试代码的编写,以免引入新的错误或影响程序的性能。 ### 回答3: debug hacks是一种用于排除软件或电脑系统中的错误的技术和工具。他们旨在帮助开发人员或IT专业人员快速而准确地识别和修正故障。debug hacks通常包括一系列的代码片段、工具和技术,可以帮助技术人员在开发和测试软件时进行调试和错误修复。 debug hacks包括很多常用的技术,例如:断点调试、日志输出、仿真调试以及追踪代码执行。这些工具都可以帮助开发人员快速定位和解决各种类型的软件错误,包括语法错误、代码逻辑错误、内存泄露和性能问题等。 debug hacks是一项非常重要的技术。在现代软件市场中,软件错误不仅影响软件的质量和用户体验,而且还会对业务产生严重的影响。因此开发人员和IT专业人员需要掌握这些技术,并应用它们来最大化软件的效益。 总之,debug hacks是一种用于排除软件故障的关键技术,可以帮助开发人员快速识别和解决软件错误。掌握这些技术可以提高软件的质量,保证软件的稳定性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值