记录开发安卓apk界面的踩坑

由于公司的手持是由原生的安卓写的,刚好想了解一下安卓,就开始长达2周的开发过程,5个页面,可能因为跟java有关系,本来是做前端开发的,自己虽然原来学过java,都忘光了...开发初期格外艰难..好在在自己努力,老大和小哥哥的帮助下,开发算是基本圆满完成了,现在来总结一下中间遇到的问题和解决方法。

问题1: 比如数字1.23或者1.26,都进行五入的操作,变成1.3该如何实现?

答: 有提供内置的BigDecimal方法,详情可见https://segmentfault.com/a/1190000002886883,介绍的很详细。

eg:
  double value1 = 1.23;
  double value2 = 1.25;
  BigDecimal bd1 = new BigDecimal(value1);
  BigDecimal bd2 = new BigDecimal(value2);
  double result1 = bd1.setScale(1, ROUND_UP).doubleValue();  //第一个参数表示保留的小数位数,第二个表示精度取值方式

  double result2 = bd2.setScale(1, ROUND_UP).doubleValue(); 

ROUND_UP: 正数是大于等于该数的那个最近数,负数是小于等于该数的那个最近数
ROUND_DOWN: 与ROUND_UP相反
还有很多其他的精度取值方式...

问题2: EditText控件,默认是获取光标位置,并弹出软键盘。不想要软键盘,但是光标位置希望一直保留闪烁,如何实现?

答:

nameText.setInputType(InputType.TYPE_NULL); 

上述代码,会隐藏软键盘,但是也会让光标消失,不可取。

后发现网上有一种根据安卓版本,来判断进行隐藏,看这篇链接:http://www.mamicode.com/info-detail-940579.html

问题3: 如何给listView中的item设置一个固定的高度?

答:
image

问题4: 布局问题:如何实现左右布局?如何实现上部内容固定,中间部分可滚动,按钮始终固定在底部呢?

答:

对于左右布局,最简单的方法就是,外层布局使用RelativeLayout,包裹的子元素分别设置android:layout_alignParentLeft="true", android:layout_alignParentRight="true"

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
  <TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignParentLeft="true"
    android:textSize="24sp"
   android:text="左边" />
   <TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignParentRight="true"
    android:textSize="24sp"
   android:text="右边" />
</RelativeLayout>

对于后者问题,即布局这样写:即

<RelativeLayout
android:layout_width="wrap_parent"
android:layout_height="wrap_parent">
<TextView
android:id="@+id/nameText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="7"
android:gravity="right"
android:textSize="22sp" />

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_parent"
    android:layout_height="wrap_parent"
    android:layout_below="@+id/nameText"
     >
       <ScrollView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/nameText"
            android:scrollbars="none">
        <LinearLayout 
                android:layout_width="wrap_parent"
                android:layout_height="wrap_parent"> 
         XXXXX.....
       </LinearLayout>
      </ScrollView>
    </LinearLayout>

   <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="5dp"
        android:orientation="horizontal">
        <Button
            android:id="@+id/resetbutton"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:background="@drawable/grey_button"
            android:ems="5"
            android:gravity="center"
            android:text="重置"
            android:textColor="@color/white"
            android:textSize="24sp" />
         <Button
            android:id="@+id/confirmbutton"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:background="@drawable/light_button"
            android:ems="5"
            android:gravity="center"
            android:text="确定"
            android:textColor="@color/white"
            android:textSize="24sp" />
   </LinearLayout>

</RelativeLayout>

即:

(1)最外层是个RelativeLayout布局,中间可滚动部分是个ScrollView,外层包裹一个LinearLayout,为这个LinearLayout设置属性: android:layout_below="@+id/nameText",表示该布局位于该TextView下面。

(2)ScrollView它的里面只能包裹一个子元素,通常是LinearLayout,取消滚动条的显示:android:scrollbars="none"

(3)底部固定的按钮外层包裹LinearLayout,为其设置
** android:layout_alignParentBottom="true"**,就可始终让其居于底部了。

问题5: listView中,如何实现展示一级和二级菜单?类似于这样:

image

但其实项目的需求是简化了的,不用点击一级菜单,再显示或者隐藏二级菜单,而是直接一次显示所有的一级菜单二级菜单,即像这样:

image

粉色部分的是一级菜单,褐色部分的是二级菜单?

答:这个问题困扰了我一天多,后来问了技术老大,了解了一下思路就解决了...还是考虑的有问题,思路没有打开啊!

按照上述图示,后台传过来的json数据是这样的:(list里面嵌套list)

{
  list: [
   {
     "name": xxx1,
     "usage": xxx1,
     "childList": [
       {
         "childName": xx1
         "code":xx1
        },
        {
         "childName": xx2
         "code":xx2
        }
      ]
   },
   {
     "name": xxx2,
     "usage": xxx2,
     "childList": [
       {
         "childName": xx3
         "code":xx3
        },
      ]
   }
  ]
}

本来想使用网上有些教程的:ExpandableListView,但是后来各种报错...只能忍痛弃之

这里还是使用的是listView,里面是listItem。

我刚开始的错误做法是,就是将数据重组,只要遇到childList, 遍历childList里面的数据,并放入map数组中,而数组本身父级的name,id也都塞进这个map数组中。

即最终形成的数据就是这样:


     "resetList": [
       {
         "childName": xx1
         "code":xx1,
         "name": xxx1,
         "usage": xxx1,
        },
        {
         "childName": xx2
         "code":xx2,
          "name": xxx1,
         "usage": xxx1,
        },
        {

         "childName": xx3
         "code":xx3,
         "name": xxx2,
         "usage": xxx2,
        },
    ]

这样在遍历时,本来第一个parent下面应该有2个子元素,第二个parent下面有1个子元素,现在遍历渲染时,即变成这样:

image

明显不对!要急哭无可奈何的时候,请教了技术老大,老大提供了这样一种思路,就是===》

把父级元素单独提取出来放在一个数组中,把孩子元素提取出来放到一个数组中,在listView重写SimpleAdapter的方法时,进行判断,如果父级元素有,设置父级元素的样式,孩子元素隐藏;如果孩子元素有,就隐藏父级元素,设置孩子元素的样式。

即数据结果变成这样:

   "resetList": [
       {
          "name": xxx1,
          "usage": xxx1,
        },
        {
          "name": xxx2,
          "usage": xxx2,
        },
        {
         "childName": xx1
         "code":xx1,
        },
        {
         "childName": xx2
         "code":xx2,
        },
        {
         "childName": xx3
         "code":xx3,
        },
    ]

Adapter接收的数据还是传递所有的字段名:


SimpleAdapter simpleAdapter = new abnormalTipsDetailAdapter2(AbnormalTipsDetailActivity.this,
                   dataList3,
                   R.layout.abnormal_tips_detail_item2,
                   new String[]{"name", "usage", "childName", "code"},
                   new int[]{R.id.name, R.id.usage, R.id.childName, R.id.code}
           );

     detail_listView.setAdapter(simpleAdapter);

在重写的SimpleAdapter的这个方法getView再进行判断:

   public class abnormalTipsDetailAdapter2 extends SimpleAdapter {
   List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
   private LayoutInflater layoutInflater;
   private Context context;
   private int hiddenType;

   public abnormalTipsDetailAdapter2(Context context, List<? extends Map<String, ?>> data,
                                     int resource, String[] from, int[] to) {
       super(context, data, resource, from, to);
       this.context = context;
       this.data = (List<Map<String, Object>>) data;
       this.layoutInflater = LayoutInflater.from(context);
   }

   public final class abnormalTipsDetailComponent {
       public TextView name;
       public TextView usage;
       public TextView childName;
       public TextView code;

       public RelativeLayout child; //子组件包裹层
       public RelativeLayout topWrapper; //父组件包裹层
   }

   @Override
   public int getCount() {
       // TODO Auto-generated method stub
       return super.getCount();
   }

   @Override
   public Object getItem(int position) {
       // TODO Auto-generated method stub
       return super.getItem(position);
   }

   @Override
   public long getItemId(int position) {
       // TODO Auto-generated method stub
       return super.getItemId(position);
   }


   public View getView(final int position, View convertView, ViewGroup parent) {
       // TODO Auto-generated method stub)
       abnormalTipsDetailComponent abnormal_detail_component = null;
       if (abnormal_detail_component == null) {
           abnormal_detail_component = new abnormalTipsDetailComponent();
           //获得组件,实例化组件
           convertView = layoutInflater.inflate(R.layout.abnormal_tips_detail_item2, null);

           abnormal_detail_component.name = (TextView) convertView.findViewById(R.id.name);
           abnormal_detail_component.usage = (TextView) convertView.findViewById(R.id.usage);
           abnormal_detail_component.childName = (TextView) convertView.findViewById(R.id.childName);
           abnormal_detail_component.code = (TextView) convertView.findViewById(R.id.code);
   
           abnormal_detail_component.topWrapper = (RelativeLayout) convertView.findViewById(R.id.topWrapper);
           abnormal_detail_component.child = (RelativeLayout) convertView.findViewById(R.id.child);

           convertView.setTag(abnormal_detail_component);
       } else {
           abnormal_detail_component = (abnormalTipsDetailComponent) convertView.getTag();
       }

       String name = (String) data.get(position).get("name"); //父组件里才有该值
       String childName = (String) data.get(position).get("childName"); //子组件里才有该值

       String usage = "";
       String code ="";

       if(name != null) {
           usage = (String) data.get(position).get("usage");

           //显示父元素
           abnormal_detail_component.topWrapper.setVisibility(View.VISIBLE);
           abnormal_detail_component.child.setVisibility(View.GONE);

           abnormal_detail_component.name.setText(name);
           abnormal_detail_component.usage.setText(usage);

       }else if(childName != null) {
           code = (String) data.get(position).get("code");

           //显示对应的子元素
           abnormal_detail_component.topWrapper.setVisibility(View.GONE);
           abnormal_detail_component.child.setVisibility(View.VISIBLE);

           abnormal_detail_component.code.setText(code + "号");
           abnormal_detail_component.code.setTextColor(Color.RED);
       }

       return convertView;
   }
}

这样就解决了。

问题6: listView中的listItem点击每一项时,发现不、无法响应对应的setOnItemClickListener方法?

答:查了百度发现,这个原因是由于:

ListViewItem中有Button或者Checkable的子类控件的话,那么默认focus是交给了子控件,而ListView的Item能被选中的基础是Item本身

所以,分为2步,

(1)为有Button或者Checkable的子类控件增加属性:

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

(2)在item最外层增加属性:

android:descendantFocusability="blocksDescendants"

问题7: 原生的android开发无论是接收数据还是传递数据,是接收json格式的数据,如何解析呢?

答:这里使用的是插件Gson来解析数据。

在app/build.gradle下,dependencies,加入该插件:

image

在File/Synchronize后,项目加入该包后,要解析或生成对应的json格式的数据,看这篇链接,https://www.cnblogs.com/liqw/p/4266209.html 写的很详细。

问题8: 页面由一个页面跳转到另一个页面,在原生的android中,使用的是Intent,通过

Intent intent = new Intent();
intent.setClass(MainActivity.this, OtherActivity.class);
startActivity(intent);
finish();

就可以实现跳转,当然这个是未涉及到页面传值,当涉及到传值时,传递的值分别是String,int,或者是一个jsonArray,接收的页面该如何解析获得值呢?

答: 这里,这样实现,先把jsonArray要转化为json字符串形式,再在接收的页面进行解析,代码实现如下:

Intent intent = new Intent();
intent.setClass(MainActivity.this, OtherActivity.class);

String waitAreaList = object.getString("list"); //这里解析jsonObject中的jsonArray的键,获取对应的值

//转化jsonArray为json字符串,项目统一使用的是Gson插件解析json数据
String str = new Gson.toJson(waitAreaList); 

//发送数据
intent.putExtra("extra", String.valueOf(123)); 
intent.putExtra("name","张三");
intent.putExtra("waitAreaList", str);
startActivity(intent);
finish();

//接收数据
 Intent intent = getIntent();
 int extra = intent.getIntExtra("extra", 0); //获取int类型的数据,为其默认赋值一个0
 String name = intent.getStringExtra("name"); //获取string类型的数据
 String stringData = intent.getStringExtra("waitAreaList"); //获取json字符串数组

 //判断json字符串是否有数据,有数据进行解析,还原成正常的jsonArray
 if(stringData.length() != 0) {
     //将JsonArray类型的Json字符串解析成对象方法
      JsonParser parser = new JsonParser();
      JsonArray array = parser.parse(stringData).getAsJsonArray();
     
     ....//执行某些方法
 }

问题9: 给textView字体加粗?

答: 对于英文字体,直接在对应的xml中,为TextView设置样式

android:textStyle="bold"

而对于中文字体,则要动态的设置:

TextView text = (TextView)findViewById(R.id.text);//或从xml导入 
text.getPaint().setFakeBoldText(true); //字体加粗

问题10: 实现类似于ios的那种swithBtn效果?switchBtn放在了listView的item,如何点击时,进行相应,请求接口?

答:

(1)对于问题1:刚开始百度了一堆,刚开始还是想搬砖,模仿网上的demo自定义实现,后来技术老大说,这个东西应该有插件,网上找一下,就不用自己写很多代码了。果断搜索后,发现了插件,github网址:https://github.com/kyleduo/SwitchButton ,例子很详细。

同理,还是现在app/build.gradle下的dependencies加入

compile 'com.github.zcweng:switch-button:0.0.3@aar'

然后在对应要使用的xml实现:

   <com.suke.widget.SwitchButton
    android:id="@+id/switchBtn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    app:sb_checked_color="#8BC34A"  //开启的背景颜色:绿色
    app:sb_enable_effect="true"  //动态效果:默认是true,开启
    app:sb_show_indicator="false" //是否显示指示器,
    app:sb_uncheck_color="#C6C7C5" /> //关闭的背景颜色:灰色

(2)对于问题2,还是考虑了一段时间的。。因为listView的数据源是通过调后端接口动态获得的,因为listItem的文字涉及到动态的改变颜色,所以得重写对应的adapter方法,adapter继承simpleAdapter方法。跟之前问题5的重写Adapter方法是一样的。

问题是,如果在重写的getView方法中,点击对应的switchBtn按钮,进行发起请求,代码会报错...那如何在本身当前页面的Activity中,进行请求?

在listView获取数据后,进行

wait_area_list_view.setAdapter(simpleAdapter);

获取list的子元素,在遍历时,遇到问题,** ListView在setAdapter()后,getChildCount总是0的原因?**

==>网上查到 setAdapater是非同步(asynchronous) 的,所以类似js promise处理这种异步的请求或数据,android也提供方法:加入post函式去更新ListView的ChildView即可。

这篇链接解决了我的问题:

http://www.cnblogs.com/linlf03/archive/2013/06/06/3120408.html

 wait_area_list_view.post(new Runnable() { //这里为了解决setAdapter是非同步(asynchronous),取得的childCount总是0的问题
        @Override
        public void run() {
            if (datalist.size() == wait_area_list_view.getChildCount()) {
               int childLen =  wait_area_list_view.getChildCount(); //获取listView下的item个数

               for(int i = 0; i< childLen; i++) {
                  RelativeLayout layout = (RelativeLayout) wait_area_list_view.getChildAt(i); //获得子item的layout
                  SwitchButton switchBtn = (SwitchButton) layout.findViewById(R.id.switchBtn); //获得switch按钮
                  final TextView areaId = (TextView) layout.findViewById(R.id.areaId);

                 //switchBtn提供当状态更改时的监听器函数
                 switchBtn.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(SwitchButton view, boolean isChecked) {
                           if(isChecked) {
                               int areaId =  Integer.valueOf(areaId.getText().toString()); //获得当前改变状态的id

                              changeStatus(areaId,isChecked); //进行对应的请求
                              xxx..
                           }                    
                     } 
                 }
               }
             }
        }
 });


//执行对应的后台请求方法
private void changeStatus(int areaId,boolean useState) {
   xxx... 
}

解决问题~

问题11: 安卓的listView如何获取item的EditText值?(因为:当动态添加数据item或者删除某个item时,由于页面会重绘,导致之前填写的edittext值被重刷消失...)

答: 这个问题还是当时来看,困扰了我半天多,还是最后在小哥哥的帮助和理清思路下,顺利解决了!真希望自己有一天,业务做的久了,在为别人解答疑惑时 都能思路清晰,经验丰富...

首先,要明确一点的是:listView中的item是复用的,就像我们看一个页面中有很多条新闻一样,不是像我们所想象的,有多少个新闻,就有多少个item渲染出来。假设页面有1000条新闻呢?页面会渲染卡死,体验很差,所以按原生的安卓来写的话,所谓复用就是: 假设listView每次就规定渲染10条item,当我们上滑加载更多时,假设此时有3条被遮挡了,那此时要加载后续的数据,安卓的实现是回收前3条被遮挡的item资源,在接下来的展示复用前面的3条,进行显示,周而复始这样循环利用,这样确保了页面的流畅性和性能的提升,不得不说,是一个很棒的思路和做法。

当然,碰到的问题就是如何获取listitem中edittext的值?

image

好的做法就是: 重写listView的Adapter.getView函数方法时 ,用一个list数组来保存Editext的值,监听EditText值的变化,当值有非空有变化时 ,就将对应的值,存储起来。其中数组的下标就是上述listItem的position值,就是上述图示显示的0,1,2...

当页面比如有数据增加进来或者删除时,我们此时不用担心数据没有,因为预先在一个数组里保存了对应的位置和数据,在页面重绘后,再重新获取list数组中的值,进行对应的setText赋值,这样就保证了数据不会丢失。

这里核心代码主要是如下所示:

image
image

博客园的这篇链接: https://www.cnblogs.com/exmyth/p/3799260.html和另外一个链接:http://blog.sina.cn/dpool/blog/s/blog_80c69e390101g221.html 也提供帮助我很多,十分感谢!

问题12: 如何实现自定义dialog?

答:请戳这篇链接:https://blog.csdn.net/zhuwentao2150/article/details/52254579

问题13: 安卓原生系统中,如何让EditText光标集中时,始终隐藏软键盘,但光标不失去焦点?

答: 这次在stackoverflow上找到了答案。

(1)设置EditText的inputType为null,隐藏了软键盘,但同时光标也消失了... 没啥用!
(2) 在AndroiManifest.xml中对应的activity中,设置:android:windowSoftInputMode="stateAlwaysHidden" 然并卵..也没啥用!
(3)当当当~
最终的高票答案来了,应该是可取的,附上代码:

// Update the EditText so it won't popup Android's own keyboard, since I have my own.
EditText editText = (EditText)findViewById(R.id.edit_mine);
editText.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        v.onTouchEvent(event);
        InputMethodManager imm = (InputMethodManager)v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
        }                
        return true;
    }
});

解决方法是通过onTouch时,检测input的变化,动态的去显示隐藏软键盘。

这里我未使用的原因是,由于使用的是listview,可能listitem会有许多个,每个item中又有EditText,处理每个时,都进行动态的判断和监听,无疑是比较耗性能的。

所以采取了另外的答案,就是设置在xml这样设置:

<EditText
android:textIsSelectable="true"
...
/>

或者设置:

EditText editText = (EditText) findViewById(R.id.editText);
editText.setTextIsSelectable(true);

对于这个属性,官方给出的解释是:

the cursor will still be present, and you'll be able to select/copy/cut/paste, but SoftKeyboard will never show但是要求安卓的sdk版本是在>=11以上

这样就解决啦~中间其实还遇到问题,刚开始只是在相应的xml中设置该属性,一台手持设备上表现正常,但另一个页面就光标和软键盘就都消失了。。排查了一圈,发现应该是手持设备sdk的版本问题导致的,所以最终不在xml设置属性,在代码里判断版本,再设置属性:

   int version = Build.VERSION.SDK_INT;
   EditText editText = (EditText) findViewById(R.id.editText);

    if (version >= 11) {
        editText.setRawInputType(InputType.TYPE_CLASS_TEXT);
        editText.setTextIsSelectable(true);
    } else {
        editText.setRawInputType(InputType.TYPE_NULL);
        editText.setFocusable(true);
    }

问题14: listView的listItem中,使用了checkbox控件,由于checkbox的获取焦点优先级高于listItem本身的setOnItemClickListener,所以希望当点击listitem时,也能选中或者不选中对应的checkbox,而不是只能点击checkbox进行选中非选中?

答:这里搜索了一圈,后来有篇博客提供了很好的答案: http://blog.sina.com.cn/s/blog_6fff321b0100otwo.html

即布局大概是这样的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/check_box_list_item"
android:minHeight="45dp"
android:orientation="horizontal"
android:descendantFocusability="blocksDescendants"
>
    <CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/boilBox"
        style="@style/BoilAreaCheckBox"
        android:background="@drawable/selector"
        android:button="@null"
        android:checked="false"
        android:focusable="false"
        android:layout_marginTop="5dp"
        android:layout_marginLeft="20dp"
        />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:textSize="20sp"
            android:id="@+id/areaName"
            android:layout_marginTop="5dp"
      />
/>

对应的activity中这样写:

     checkbox_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
   //         LinearLayout layout = (LinearLayout) adapterView.getChildAt(position);
   //         CheckBox checkBox = (CheckBox) layout.getChildAt(0);
            CheckBox checkBox = (CheckBox) view.findViewById(R.id.boilBox);
            checkBox.setChecked(!checkBox.isChecked());
        }
    });   

注意:

(1): 这里的 checkbox_list是listView的id名称;

(2): 这里setItem时,去动态监听布局,拿到每次点击的positon对应的LinearLayout; 由于CheckBox控件都是布局的第一个元素,所以是 CheckBox checkBox = (CheckBox) layout.getChildAt(0);

(3): 通过setChecked控制选中,通过isChecked()判断当前是否选中

(4): checkBox获取焦点的优先级,不要抢占点击listitem的优先级,即对于checkbox设置属性:

android:checked="false"
android:focusable="false"

而对于listItem层级,设置属性

android:descendantFocusability="blocksDescendants"

这里在之后发现大bug!!注意注意!!,就是listview中使用checkbox,当数据多时,由于listview本身的复用item的特性,导致getChildAt会报空指针!!

所以之前的代码用斜杠注释掉了,现在点击某一个item下的checkbox,直接在setOnItemClickListener中找到当前view下的checkBox,之后再进行选中非选中操作。

问题15: 如果让edittext的光标始终集中在输入文字的末尾?

答: 添加该行代码:

EditText et = (EditText)R.id.findViewById(R.id.edit);
et.setSelection(et.getText().length()); //让光标在文字末尾

问题16: 当listview中使用了checkbox时,由于listitem的复用的特性,导致勾选的状态混乱或者勾选状态消失,如何解决?

答:虽然这个问题百度和stackoverflow上一搜一大堆,但是质量良莠不齐的,耽误了2天的时间搜索答案,结果中午吃饭的时候突然有了思路,外加查到一篇好的博文,终于解决问题!!大块人心!!

这里好好总结处理一下:

这篇博文: https://blog.csdn.net/jdsjlzx/article/details/7318659 帮助很多!强烈建议一看啊!

我最终的解决方法也是模仿文章的方法来做的:

这里,首先重写Adapter类,继承自SimpleAdapter,这里,用一个HashMap<Integar,Boolean>来保存checkbox的勾选状态,默认初始都为false

public class BindBoidAreaAdapter extends SimpleAdapter {

List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
private LayoutInflater layoutInflater;
private Context context;

private static HashMap<Integer, Boolean> isSelected = null;  //用来控制CheckBox的选中状况

public BindBoidAreaAdapter(Context context, List<? extends Map<String, ?>> data,
                           int resource, String[] from, int[] to) {
    super(context, data, resource, from, to);
    this.context = context;
    this.data = (List<Map<String, Object>>) data;
    this.layoutInflater = LayoutInflater.from(context);

    isSelected = new HashMap<Integer, Boolean>();

    initStatus();
}

// 初始化isSelected的数据,默认都为未勾选状态
private void initStatus() {
    for (int i = 0; i < data.size(); i++) {
        getIsSelected().put(i, false); 
    }
}

//向activity暴露checkbox的勾选状态,返回这个HashMap
public static HashMap<Integer, Boolean> getIsSelected() {
    return isSelected;
}

public final class bindBoidAreaComponent {
    public CheckBox boilBox;
    public TextView areaName;
    public TextView areaId;
}

@Override
public int getCount() {
    // TODO Auto-generated method stub
    return super.getCount();
}

@Override
public Object getItem(int position) {
    // TODO Auto-generated method stub
    return super.getItem(position);
}

@Override
public long getItemId(int position) {
    // TODO Auto-generated method stub
    return super.getItemId(position);
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    final int pos = position;
    // TODO Auto-generated method stub)
    bindBoidAreaComponent bind_boil_area = null;
    if (bind_boil_area == null) {
        bind_boil_area = new bindBoidAreaComponent();
        //获得组件,实例化组件
        convertView = layoutInflater.inflate(R.layout.check_box_list_item, null);

        bind_boil_area.boilBox = (CheckBox) convertView.findViewById(R.id.boilBox);
        bind_boil_area.areaName = (TextView) convertView.findViewById(R.id.areaName);
        bind_boil_area.areaId = (TextView) convertView.findViewById(R.id.areaId);

        convertView.setTag(bind_boil_area);
    } else {
        bind_boil_area = (bindBoidAreaComponent) convertView.getTag();
    }


    bind_boil_area.boilBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
      @Override
       public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
          getIsSelected().put(pos,isChecked); //用来解决listView在滚动时,checkbox状态混乱的问题
       }
    });

    //绑定数据
    String areaName = (String) data.get(position).get("areaName");
    Integer areaId = (Integer) data.get(position).get("areaId");

    bind_boil_area.areaName.setText(areaName);
    bind_boil_area.areaId.setText("" + areaId);

    //根据isSelected设置checkbox的选中
    bind_boil_area.boilBox.setChecked(getIsSelected().get(pos));

    return convertView;
  }

}

上述adapter方法中,最重要的几个方法,

(1)一个是构建HashMap,初始化checkbox的state, 方法为initStatus();

(2) 如果要将adapter中设置的HashMap,在activity中 也能使用,则要写一个public static方法,返回这个HashMap,之后就可以在activity中,BindBoidAreaAdapter.getIsSelected(),访问得到我们设置的HashMap。(当时思路就卡在这里了!不知道如果去访问adpater中的设置的这个数据,恩,还是有收获的)

这里就涉及到android的static,是静态变量,一般静态变量用的不多,静态常量用的比较多。

static即始终会保存在内存里,不会随着activity的销毁被回收,会占用内存。static的使用,是为了让多个对象共用一份空间,节省内存,由于不正确的操作或者编码不规范,会造成内存泄露。。但这里先不深入那么多...

(3)特别注意,中间的针对于checkbox的setOnCheckedChangeListener监听的方法中,

getIsSelected().put(pos,isChecked); //该行代码,、在滚动时,动态设置checkbox的选中非选中为true 或者false

   //根据isSelected设置checkbox的选中
  bind_boil_area.boilBox.setChecked(getIsSelected().get(pos));

再来看Activity方法中,接收的数据是这样的,后台会传一份全部的数据list,和(如果之前有勾选checkbox)传递一个checkedList数组,反之传来的checkedList为空字符串。

我这里具体的做法是,请求数据获得全部的数据后,先去动态绘制出这个listview

private void getData() {
     xxx....
     setAllCheckViews();

    //这里前面加判断,如果有勾选的list,则去显示勾选的list
    checkbox_list.post(new Runnable() { //这里为了解决setAdapter是非同步(asynchronous),取得的childCount总是0的问题
       @Override
        public void run() {
         checkedBoxView(checkedArray);
      }
    });
}


//设置全部的checkbox名称与id
private void setAllCheckViews() {
    int allcount = allArray.size();
    for (int i = 0; i < allcount; i++) {
        Map<String, Object> map = new HashMap<String, Object>();
        String areaName = allArray.get(i).getAsJsonObject().get("areaName").getAsString();
        int areaId = allArray.get(i).getAsJsonObject().get("areaId").getAsInt();

        map.put("areaName", areaName);
        map.put("areaId", areaId);
        allList.add(map);
    }

    simpleAdapter = new bindBoidAreaAdapter(BindBoilAreaActivity.this,
            allList,
            R.layout.check_box_list_item,
            new String[]{"areaName", "areaId"},
            new int[]{R.id.areaName, R.id.areaId}
    );

    checkbox_list.setAdapter(simpleAdapter);

}

然后,listView中的各个item显示出来后,再进行具体的勾选状态:

private void checkedBoxView(JsonArray checkedArray) {
    int allCount = allArray.size();
    int checkedCount = checkedArray.size();
    for (int i = 0; i < allCount; i++) {
        for (int j = 0; j < checkedCount; j++) {
            int allId = allArray.get(i).getAsJsonObject().get("areaId").getAsInt();
            int checkId = checkedArray.get(j).getAsJsonObject().get("areaId").getAsInt();
            if (allId == checkId) { //勾选对应checkbox
                BindBoidAreaAdapter.getIsSelected().put(i, true); //这里就是通过双重循环比对id,若id相同,则设置对应的checkbox为选中
            }
        }
    }
    simpleAdapter.notifyDataSetChanged(); //重绘一下页面,否则页面观察不到变化!
}

上述代码中,只有通过:

BindBoidAreaAdapter.getIsSelected().put(i, true);

该行代码,BindBoidAreaAdapter这个类才能访问到里面构造的静态方法getIsSelected,从而拿到BindBoidAreaAdapter类中预先定义好的静态变量hashMap,进行对应的更改操作。

但很奇怪,在自己的理解中,在类中定义一个public的方法,按理说不需要static修饰,在activity中实例化这个类的对象,就可以通过对象访问到该方法了。

但当我去掉BindBoidAreaAdapter中对于isSelected这个hashMap的static的修饰,同时也去掉对于方法名getIsSelected的static的修饰时,Activity中

BindBoidAreaAdapter.getIsSelected().put(i, true); 这行代码就会报错,说Non-static method 'getIsSelected' cannot be referenced from a static context.

即后来问了小哥哥,讨论了一下,看书了解到,即只有静态方法可以访问到自身类中的静态域

就比如这个例子

public static int getId() { return nextId; // return static field }

此时假设包含这个静态方法getId的类叫Employee,我们就可以通过

int id = Employee.getId(); //获得数据

可以使用对象调用静态方法,比如我们此时可以

BindBoidAreaAdapter bindBoidAreaAdapter = null; //bindBoidAreaAdapter为BindBoidAreaAdapter的对象

在循环中,再

bindBoidAreaAdapter.getIsSelected().put(i, true);

不过书上讲,这种方式很容易造成混淆,原因是getIsSelected得到的结果跟bindBoidAreaAdapter没有任何关系。

书上建议:

建议使用类名,而不是对象来调用静态方法。

书上同样指出,以下两种情况使用静态方法:

(1)一个方法不需要访问对象状态,其所需参数都是通过显式参数,如Math.pow(x,a),就是一个静态方法

(2)一个方法只需要访问类中的静态域,比如我们之前所写代码的:BindBoidAreaAdapter.getIsSelected().put(i, true);

这里原来自己老的做法是,双重循环遍历,然后

private void checkedBoxView(JsonArray checkedArray) {
    int allCount = allArray.size();
    int checkedCount = checkedArray.size();
    for (int i = 0; i < allCount; i++) {
        for (int j = 0; j < checkedCount; j++) {
           int allId = allArray.get(i).getAsJsonObject().get("areaId").getAsInt();
           int checkId = checkedArray.get(j).getAsJsonObject().get("areaId").getAsInt();

          LinearLayout layout = (LinearLayout) checkbox_list.getChildAt(i); 
          CheckBox checkBox = (CheckBox) layout.findViewById(R.id.boilBox);
                    
         if(allId = checkId) {
          checkBox.setChecked(true);
        }
   }
 }
}

这里当时就报nullPointerException了,比如现在allArray现在是9个,而页面可视区域只有7个,getChildAt最多定位的postion只有7,当循环遍历到8和9时,listview找不到,就报错了。。。

解决报错的方法:即网上说的很多,positon要为可视区域的position,有2种解决方法:

一种是: 即最外层的循环,allCount并非 allArray.size(),而是listView可视区域的数量,

int allCount = checkbox_list.getLastVisiblePosition() - checkbox_list.getFirstVisiblePosition(); 

另一种是:内部的getChildAt,使用,

int positon = i - checkbox_list.getFirstVisiblePosition(); 
LinearLayout layout = (LinearLayout) checkbox_list.getChildAt(positon); 

虽然上述2种做法可以解决不报错了,但是还是没有解决勾选对应的应选中的checkbox,还是会复用item,checkbox的状态混乱,所以。。舍弃,但记录下来,为防止以后碰到类似问题。

一个bug解决了2天多...最终有学到东西,还是挺好的,希望自己之后安卓这部分做好,还是回归前端,好好夯实前端基础吧。

问题17: 如何让ScrollView下的内容都能居中显示呢?

答:百度搜了一圈,没有很靠谱的,后来同样是在stackoverflow上找到答案。

问题描述,类似于这样:

image

好几个优质的答案,我选用的是第一种,即设置linearlayout的中的属性

android:gravity="center_horizontal"

android:layout_gravity="center_horizontal"

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:paddingTop="12dp"
    android:paddingBottom="20dp"
    android:scrollbarStyle="outsideOverlay" >


    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" 
        android:layout_gravity="center"
        android:gravity="center">

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="check" 
            android:gravity="center_vertical|center_horizontal"/>
    </LinearLayout>

</ScrollView>

第二种好的做法是,在ScrollView外面再包裹一层Relativelayout,为了让其生效,首先要设置ScrollView下的layout_height:wrap_content,

其次,对于ScrollView,实际上此时为RelativeLayout的子元素,要使其居中,设置属性 android:layout_centerVertical="true"

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<ScrollView
    android:id="@+id/scrollView1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" 
    android:layout_centerVertical="true" >

 <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="wrap_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
</LinearLayout>
</ScrollView>
</RelativeLayout>

但个人感觉这样,一层层嵌套包裹,实际上页面渲染会变慢,最好的方式实际上是页面结构越简单越好。

问题18: 遇到一个问题,就是安卓的页面中有EditText控件,在它的下面又有spinner控件,EditText控件 在第一次输入后,按Enter键表现正常,当第二次输入按enter键,则光标会定位到spinner控件上,如何处理?

答: 这里也不知道是不是手持设备的原因,最终是为EditText控件设置属性: android:nextFocusDown="@id/soakEdit",即下次光标定位还是在其本身,就可以解决这个问题了。

<EditText
        android:id="@+id/soakEdit"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:background="@null"
        android:ems="8"
        android:hint="@string/soak_scan"
        android:nextFocusDown="@id/soakEdit"
        android:textSize="23sp" />

问题19: 使用原生安卓的button时,button使用的是一张背景图。当发起请求,由于请求是异步的,得到结果有延迟,导致用户以为没有效果,会多点按钮,体验效果差。更好的体验是:发起请求有个loading的状态,得到返回结果后隐藏这个这个loading状态。

答:开始考虑这个问题的时候,跟之前做过的switchButton一样,首先应该考虑的是有无这样的button插件实现。

网上搜到了这种效果的progressButton,但是在设置build.gradle,该插件的依赖时,各种报错,没有办法解决,所以最终弃之。

后来在stackoverflow上,找到提供思路的答案,即使用安卓原生的组件progressBar,具体链接,参考:https://stackoverflow.com/questions/12559461/how-to-show-progress-barcircle-in-an-activity-having-a-listview-before-loading

这个高票答案写的很棒,我也基本上按照这个思路来:

image

即:

  1. 先在xml里设置好ProgressBar的布局,默认隐藏;
  2. 在activity里,初始化该ProgressBar
  3. 发送请求时,显示这个ProgressBar,得到请求结果时,隐藏该ProgressBar。

中间出现的问题,可能集中在布局这里,原来只是button设置background为drawable下的图片资源,ProgressBar也可以放在按钮的前面,但是问题是分离了,loading未包含在btn里面。

后来好的解决方法:

即将ProgressBar和Button都放在一个Linearlayout布局中,就像这样:

 <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:background="@drawable/bh_button_shape"
            >
            <ProgressBar
                android:id="@+id/pbNormal"
                android:layout_width="35dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="15dp"
                android:visibility="invisible"
                />

            <Button
                android:id="@+id/bindbutton"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:background="@android:color/transparent"
                android:text="绑定"
                android:layout_marginRight="45dp"
                android:textColor="@color/white"
                android:textSize="24sp" />
</LinearLayout>

具体步骤:
1、为LinearLayout设置背景为原来的button背景图,orientation设置为水平horizontal
2、ProgressBar默认不显示
3、Button设置背景为透明,android:background="@android:color/transparent"
4、具体再调整样式细节。

这中间还遇到一个问题,如何让一个按钮始终居于页面底部呢?
两种方式:

第一种:

如果是Linearlayout布局,则给上面的Linearlayout设置android:layout_weight="1" ,这样可以让底部的button到达底部。

第二种:

使用Relativelayout布局,对于Button设置属性=》,android:layout_alignParentBottom="true"

问题20: 如何让两个TextView在页面上实现左右平分布局?

答:百度查的答案都良莠不齐,后来在stackoverflow上找到答案。

参考链接在这里:https://stackoverflow.com/questions/13441405/android-how-to-split-between-two-textview-the-full-width-of-screen-exactly-fifty

总结一下,有2种方案:

第一种:

用一个Linearlayout包裹这2个TextView,设置layout_width为fill_parent,给子TextView要么设置layout_weight为1要么都不设置这个layout_weight。

代码如下:

<LinearLayout 
     android:layout_width = "fill_parent"
    android:layout_height = "wrap_content"
     orientation = "horizontal">
    <TextView
        android:layout_width = "fill_parent"
        android:layout_height = "wrap_content"
        android:text = "Text 1"
    />
    <TextView
        android:layout_width = "fill_parent"
        android:layout_height = "wrap_content"
        android:text = "Text 2"
    />
</LinearLayout>

第二种:(比较高票,我也使用的这个,实现了平分布局的效果)

代码如下:

<RelativeLayout
    android:layout_width = "fill_parent" 
    android:layout_height = "wrap_content">
    <LinearLayout 
    android:layout_width = "fill_parent" 
    android:layout_height = "wrap_content" 
    android:orientation="horizontal"
    android:weightSum = "2">
        <TextView
            android:layout_width = "fill_parent"
            android:layout_height = "wrap_content"
            android:text = "YOUR FIRST TEXT"
            android:weight = "1"
        />
        <TextView
            android:layout_width = "fill_parent"
            android:layout_height = "wrap_content"
             android:text = "YOUR SECOND TEXT"
            android:weight = "1"
        />
    </LinearLayout>

</RelativeLayout>

该方法的要点有3个:

  1. 设置他们的layout_width均为fill_parent
  2. 给外层包裹的Linearlayout设置权重总和android:weightSum = "2",android:orientation="horizontal"
  3. 设置子TextView的android:weight = "1"

答案这么介绍这个weight sum属性:

   the weight sum will be used to measured the portion of the textview like if you give the weight sum 3 and give 1 textview weight = 2 and another textview weight = 1 the layout will be 2/3 (66.666%) and 1/3 (33.3333%) so if you set it 2 and give every textview weight = 1 it should be 50% and 50%

问题21: 如何将一个JSONArray中的值取出来,添加到一个字符串数组里?如何将一个字符串数组转化为以逗号分隔的字符串?

答:同样是在stackoverflow上找到答案,国外的answer,真是很棒~

这里,原来百度查到的,将字符串数组变为字符串,可以用StringUtils.join(',',strArray);

但后来查到: StringUtils.join(',',strArray) 是Jave 8里添加的一个方法,所以不能用在Android里。

可以用TextUtils.join来代替,

String result = TextUtils.join(", ", list); //list

所以最终所有问题的实现,代码如下可以解决:

ArrayList<String> stringArr = new ArrayList<String>();
JSONArray jsonArray = object.getJSONArray("list");
int count = jsonArray.length();
for(int i = 0; i < count;i++) {
  String code = jsonArray.getJSONObject(i).getString("code");
  stringArr.add(code);
}

String[] stringArr = stringArr.toArray(new String[stringArr.size()]);
String bucketStr = TextUtils.join(",",stringArr);

问题22: (1)安卓的spinner下拉控件,原来是用一个背景图片替换原生的spinner样式,但是由于固定了宽度等,当文字变多时,文字会被遮挡住,如何优化样式?
(2)如何让spinner下拉选项的文字(默认是靠左的),实现居中显示?

答: 对于(1),(2)找了一圈,后来在stackoverflow上找到答案,spinner此时不用设置background为图片,用定义的样式xml文件实现。具体看代码:

对于问题1:在spinner外包裹一个LinearLayout,为这个LinearLayout设置background

<LinearLayout
    android:layout_width="200dp"
    android:layout_height="40dp"
    android:background="@drawable/bg_spinner"
    >
  <Spinner
      android:id="@+id/print_spinner"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@null"
      />
</LinearLayout>

背景样式文件,放在drawable文件下的bg_spinner.xml中

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:top="2dp">
        <shape>
            <corners android:radius="2dp"/>
            <stroke
                android:width="2dp"
                android:color="#A4A4A4" />
            <padding
                android:top="2dp"
                android:right="2dp"/>
        </shape>
    </item>
    <item android:right="4dp">
        <bitmap android:gravity="right|center_vertical"
            android:src="@drawable/custom_spinner_icon"
            >
        </bitmap>
    </item>
</layer-list>

注: 第一个item是设置spinner的圆角大小,边框颜色粗细和padding,第二个item是设置旁边的倒着的小三角箭头

对于问题2,如何使spinner下拉选项Item中的文字居中?默认是靠左显示的。

第一步,设置一个xml文件命名为spinner_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:textColor="#929292"
    android:textSize="22sp"
    android:gravity="center"
    android:textAlignment="center"
    />

注意:这里之前设置了属性android:gravity="center",android:textAlignment="center,但是文字就是不居中,查阅了半天,后来发现原来设置android:layout_width和android:layout_height为wrap_content,改为match_parent后就生效了。

第二步,在java类中,调用spinner的时候,使用自定义的这个xml下拉

ArrayAdapter<String> adapter= new ArrayAdapter<String>(context, R.layout.spinner_item, myList);
adapter.setDropDownViewResource(R.layout.spinner_item)
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容