Android-Issues

2017/5/28 posted in  Android

前言

这篇文章主要记录一些在实际开发中遇到的问题,以及对其的解决措施。方便日后解决同样类型的问题。

Activity的使用

1、onSaveInstance 和 onRestoreInstanceState

onSaveInstance方法在系统回收Activity之前会调用;Back键不会调用。onPause()—>onSaveInstanceState( )–>onStop( );onRestoreInstanceState()会在onStart()和onResume()之间执行。

  • 1、当用户按下HOME键时。这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
  • 2、长按HOME键,选择运行其他的程序时。
  • 3、按下电源按键(关闭屏幕显示)时。
  • 4、从activity A中启动一个新的activity时。
  • 5、屏幕方向切换时,例如从竖屏切换到横屏时。在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行

至于onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和 onRestoreInstanceState方法“不一定”是成对的被调用的,onRestoreInstanceState被调用的前提 是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行。
activity中保存数据有两种方式onPause(),onSaveInstance(bundle),  恢复数据也有两种途径onCreate(Bundle), onRestoreInstanceState(budle),默认情况下onSaveInstanceSate()和onRestoreInstanceState()会对UI状态进行保存和恢复,如果需要保存其他数据可以在onSaveInstanceState(),onPause()保存,但是如果是持久化的数据得通过onPause()保存(google推荐)。

二、Fragment使用

1、java.lang.IllegalStateException: Fragment does not have a view

先说明下出现这个异常的原因,这是在fragment onCreateView()中调用getChildFragmentManager()的时候出现的。解决办法就是不要再onCreateView()中调用getChildFragmentManager()。那么如果你要调用getChildFragmentManager(),那么必须onCreateView之后,view被创建了。

三、UI控件

1、Button控件

(1)解决Android5.0版本以上Button自带阴影问题

设置Button的样式为style="?android:attr/borderlessButtonStyle"

2、TextView控件

(1)Text View中修改文字段落中部分字体颜色

使用ForegroundColorSpan来修改

3、CheckBox控件

(1)如何修改复选框的样式

  • 首先在drawable文件夹中添加drawable文件checkbox_style.xml。

    <?xml version="1.0" encoding="utf-8"?>  
    <selector xmlns:android="http://schemas.android.com/apk/res/android">  
    <item android:drawable="@drawable/checkbox_pressed" android:state_checked="true"/>  
    <item android:drawable="@drawable/checkbox_normal" android:state_checked="false"/>  
    <item android:drawable="@drawable/checkbox_normal"/>  
    </selector>
  • 在values文件夹下的styles.xml文件中添加CustomCheckboxTheme样式。

<style name="CustomCheckboxTheme" parent="@android:style/Widget.CompoundButton.CheckBox">  
    <item name="android:button">@drawable/checkbox_style</item>  
</style>
  • 在布局文件中使用CustomCheckboxTheme样式。 xml <CheckBox  
    android:id="@+id/select_all"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    style="@style/CustomCheckboxTheme" />

4、RecyclerView控件

(1)RecyclerView删除Item导致位置错乱

RecyclerView的刷新分为内容变化和结构变化,结构变化比如remove和insert等并不会导致viewholder的更新,所以有时候我们使用notifyItemRemoved(position);或者使用notifyItemInserted(position);item的位置并没有发生改变,或者位置发生错乱,很是奇怪诡异,需要重新调用notifyDataSetChanged();才能刷新整个List每个Item的位置,但这样做会使得RecyclerView增加和删除的动画效果没有了。那么要既想没有Bug的插入删除,又想有动画怎么搞呢,只需要刷新删除位置以下的List的Item位置即可,那么幸亏RecyclerView有一个局部刷新的方法:notifyItemRangeChanged(int positionStart, int itemCount)怎么使用呢? 我们只需要在删除或插入时同时,刷新改变位置item下方的所有Item的位置: 插入动作:

notifyItemInserted(position);
if (position != mData.size()) {
   otifyItemRangeChanged(position, mData.size() - position);
 }

删除动作:

 notifyItemRemoved(position);
if (position != mData.size()) {
   otifyItemRangeChanged(position, mData.size() - position);
 }

(2)RecyclerView只显示第一个数据

onCreateViewHolder中获取View的时候不指定ViewGroup

@Override  
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());  
    View convertView = layoutInflater.inflate(R.layout.item, null, false); // if use parent, only first line will show up  
    return new ViewHolder(convertView);  
}

5、EditText控件

(1)如何自动打开软键盘

四、自定义View的实现

1、自定义TextView时,自定义参数的设置

自定义属性一般在onMeasure()方法后才能将XML文件中的自定义设置的属性数值传递进来,无法在setBackground()方法和setText()方法直接调用。如果是想要在代码中设置变量属性,还是直接在自定义控件扩展类中添加新方法进行使用。自定义控件的三个构造类

public GuideTextView(Context context) //在代码中直接使用
public GuideTextView(Context context, AttributeSet attrs) //在xml文件中直接使用时调用
public GuideTextView(Context context, AttributeSet attrs, int defStyleAttr) //xml文件中使用默认主题时调用

2、扩展自定义View的实现

onDraw()方法中,将绘制代码写在super.onDraw(canvas);方法之前,绘制效果将显示在控件底层;将绘制代码写在super.onDraw(canvas);方法之后,绘制效果将显示在控件底层。

3、getDimension()、getDimensionPixelOffset()和getDimensionPixelSize()的区别

  • getDimension()是基于当前DisplayMetrics进行转换,获取指定资源id对应的尺寸。文档里并没说这里返回的就是像素,要注意这个函数的返回值是float,像素肯定是int。
  • getDimensionPixelSize()getDimension()功能类似,不同的是将结果转换为int,并且小数部分四舍五入。
  • getDimensionPixelOffset()getDimension()功能类似,不同的是将结果转换为int,并且偏移转换(offset conversion,函数命名中的offset是这个意思)是直接截断小数位,即取整(其实就是把float强制转化为int,注意不是四舍五入哦)。 由此可见,这三个函数返回的都是绝对尺寸,而不是相对尺寸(dp/sp等)。如果getDimension()返回结果是20.5f,那么getDimensionPixelSize()返回结果就是21,getDimensionPixelOffset()返回结果就是20。

4、DisplayMetrics显示的数值

  • density: 显示的逻辑分辨率
  • widthheight: 屏幕分辨率(绝对宽高)
  • scaleDensity: 字体显示的缩放因子
  • xdpiydpi: 水平方向DPI和竖直方向DPI

5、屏幕尺寸单位

  • 屏幕尺寸: 屏幕尺寸指屏幕的对角线的长度,单位是英寸(in),1英寸=2.54厘米
  • px: 是英文单词pixel的缩写,意为像素,屏幕上的点。我们通常所说的分辨率如480X800就是指的像素,一般以纵向像素*横向像素。
  • dpi: dpi是Dots Per Inch的缩写, 每英寸点数,即每英寸包含像素个数。
  • density: 屏幕密度,density和dpi的关系为 density = dpi/160
  • dp和dip: 设备独立像素,device independent pixels的缩写, Android 特有的单位,在屏幕密度dpi = 160屏幕上,1dp = 1px。dp和density的关系为 1dp = density px,dip值 =(dpi值/160)* pixel值
  • sp: 和dp很类似,一般用来设置字体大小,和dp的区别是它可以根据用户的字体大小偏好来缩放。

6、EditText启动软键盘怎么和EditText布局一起上移,并且背景不动

在根布局中添加ScrollView来存放背景显示内容,中间必须添加一个RelativeLayout或者LinearLayout(如果添加RelativeLayout则ScrollView必须设置属性android:fillViewport="true"),在ScrollView控件底下添加EditText所在布局,设置AndroidManifest.xml中android:windowSoftInputMode="adjustResize|stateHidden",在代码中设置RelativeLayout中的显示布局的布局属性。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.jrm.adolph.test1.MainActivity">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">
        <RelativeLayout
            android:id="@+id/layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="64dp"
                android:background="@color/colorAccent">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:text="软键盘"
                    android:textSize="20sp"/>
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/content_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Hello World!"
                    android:layout_centerInParent="true"
                    android:textSize="20sp"/>
            </RelativeLayout>
        </RelativeLayout>
    </ScrollView>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_alignParentBottom="true">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="送礼"/>
        <EditText
            android:id="@+id/et"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送"/>
    </LinearLayout>
</RelativeLayout>
Rect outRect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) layout.getLayoutParams();
params.height = outRect.bottom - outRect.top;
layout.setLayoutParams(params);

7、设置Button的selector控制按钮的点击形态

设置好press、enable属性的对应图片,点击按钮图片形态不响应。必须把默认的按钮图片样式放在最底下,才能实现。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/lucky_turntable_pressed_btn" android:state_pressed="true"/>
    <item android:drawable="@drawable/lucky_turntable_unable_btn" android:state_enabled="false"/>
    <item android:drawable="@drawable/lucky_turntable_focusable_btn"/>
</selector>

8、DispatchTouchEvent

重写dispatchTouchEvent的时候,无论你是return true,亦或是return false都会导致child接受不到事件。
return true : 告诉parent,这个事件我消费了。如果这个是down事件,那么我就会作为一个target或者说handle(事件持有者),后续的move事件或者up事件等,都会直接分发到我这里,不继续往下分发。 
return false:告诉parent,这个事件我不需要,那么会交回给parent的onTouchEvnet处理。只有return super.dispatchTouchEvent的时候才会将事件继续往下传递。

20170923150616205533627.jpg
20170923150616205533627.jpg

五、Android类库使用问题

1、Android DataBinding

(1)include布局使用,如何传变量进去

include布局中同样用layout标签包裹,将父布局中的变量以

app:XXX="@{XXX}"

自定义属性的形式传进去,并在include布局中同样申明XXX相同的变量名以及变量类型,即可进行使用。注意使用在表达式中使用View.VISIBLE设置可视化的时候导入View类,否则会报错。

(2)如何调用include中的控件

普通调用include中的控件,直接使用findViewById可以直接获取控件。使用dataBinding时,有时会出现获取控件无法调用的情况,这种情况给include标签赋一个id,在类中通过bind.<include-id>.<widget-id>进行调用。