fragment 不重叠

写过类似微博这种tab结构的人都知道,使用的是fragment实现,但是fragment有个问题,就是官方其实没有说明白这个东西到底应该怎么用。比如如果简单的使用rplace来切换tab切换fragment会导致每次都会刷新的问题
使用hide和show显示fragment会带来重叠的问题。

为什么会出现重叠的现象?

因为Activity在内存不足的时候被回收了,再次打开的时候恢复了Activity,之前的Fragment还在Activity中,但是你又重新创建了Fragment,并且Add回去了,所以就重叠了。

继续阅读“fragment 不重叠”

TabHost的编写过程

TabHost 一个类似于苹果上TabBar的上东西。

开发原因
虽然不提倡在Android上使用这种方式,但是无奈要使用的场景太多了。
以前的解决方案是使用RadioGroup ,这个解决方案比较快,但是问题也有,就是无法显示小红点。
粗暴的解决方案是使用布局控件,LiearLayout 包裹着几个个RelativeLayout,然后就可以解决了。
以上的解决每次都要自己写一次(当然,很多人其实一年也只是写一次而已),但是我就是觉得很烦,而且上面的组合方式一定是用到了Fragment,还要管理Fragment的恢复和保存,不然就要等着回复Activity实例的时候Fragment的各种错位。

解决的问题
主要解决的问题的是Fragment的保存和恢复的问题,其次才是一下杂七杂八的问题

TabHost 架构

界面层
TabHost Tab 这些元素组成了,TabBar界面的现实
模型层
FragmentTabAdapter 完成了整个外部界面的显示和切换,在getFragment 中提供Fragment

当界面回收的时候,FramgmentTabAdapter会把Fragment装换到Bundle中,通过TabHost的outState 保存,
当恢复的时候通过TabHost的onRestoreInstance函数调用FragmentTabAdapter的onrestoreInstance 调用。

讲讲代码

  1. 创建CheckableLayout.java
    这是一个响应选中事件的RelativeLayout,用来当Tab的父类
package me.hangox.tabhost;


import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Checkable;
import android.widget.FrameLayout;


import java.util.ArrayList;
import java.util.List;




/**
 * @author 47
 *         继承于RelativeLayout,
 *         主要作用是使布局中其中一个View
 */
public class CheckableLayout extends FrameLayout implements Checkable {
    public static final String TAG = CheckableLayout.class.getSimpleName();


    private static final int[] LAYOUT_STATE = {
            android.R.attr.state_checked,
    };


    private List<Checkable> mCheckableList = new ArrayList<>();


    private boolean mChecked;


    public CheckableLayout(Context context) {
        super(context);
    }


    public CheckableLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    public CheckableLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }




    @Override
    public void setChecked(boolean checked) {
        if (mChecked == checked) return;
        mChecked = checked;
        setChildClicked(checked);
        refreshDrawableState();


    }


    private void setChildClicked(boolean checked){
        for(Checkable checkable : mCheckableList){
            checkable.setChecked(checked);
            ((View)checkable).refreshDrawableState();
        }
    }


    public void addCheckableView(Checkable checkable){
        if(checkable instanceof View){
            mCheckableList.add(checkable);
        }
    }


    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, LAYOUT_STATE);
        }
        return drawableState;
    }


    @Override
    public boolean performClick() {
        toggle();
        return super.performClick();
    }


    @Override
    public boolean isChecked() {
        return mChecked;
    }


    @Override
    public void toggle() {
        //防止双击的时候再次选中
        if (!isChecked()) setChecked(!mChecked);
    }




}

这里比较困难的地方在于,RelativeLayout 原本是不支持Checkable 这个状态的,需要添加代码来响应这个状态,具体代码参考onCreateDrawbleState().

  1. 创建Tab.java
    Tab是用来显示一个个的按钮的,外带小红点功能
package me.hangox.tabhost;


import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;


import com.hangox.xlog.XLog;


import org.hangox.tabhost.R;




/**
* Created With Android Studio
* User @47
* Date 2014-10-21
* Time 22:29 5
*/
public class Tab extends CheckableLayout {
public static final String TAG = Tab.class.getSimpleName();
/**
* 显示文字作用
*/
private CheckedTextView mCheckText;


/**
* 自带背景红点功能
*/
private TipView mTipView;
private OnCheckedChangeListener mCheckChangedListener;
private int mTipBackgroundColor = 0xFFFC5C3F;
private int mTipCount = -1;




public int getIndex() {
return mIndex;
}


public void setIndex(int mIndex) {
this.mIndex = mIndex;
}


private int mIndex;


public Tab(Context context) {
super(context);
init(null, 0);
}


public Tab(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}


public Tab(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, defStyleAttr);
}


private void init(AttributeSet attrs, int defStyle) {
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.Tab, defStyle, 0);
Drawable icon = a.getDrawable(R.styleable.Tab_tabIcon);
String tabName = a.getString(R.styleable.Tab_tabName);
float tabTextSize = a.getDimension(R.styleable.Tab_tabTextSize, TypedValue.applyDimension
(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
ColorStateList tabTextColor = a.getColorStateList(R.styleable.Tab_tabTextColor);


mTipBackgroundColor = a.getColor(R.styleable.Tab_tipBackgroundColor, mTipBackgroundColor);
int tipTextColor = a.getColor(R.styleable.Tab_tipTextColor, Color.WHITE);


a.recycle();
//initView
//初始化文字View
Context context = getContext();
View rootView = View.inflate(context, R.layout.tabhost_tab, null);
//icon
CheckedImageView iconView = (CheckedImageView) rootView.findViewById(R.id.tabIcon);
iconView.setImageDrawable(icon);
addCheckableView(iconView);
//tabName
mCheckText = (CheckedTextView) rootView.findViewById(R.id.tabName);
mCheckText.setText(tabName);
if (tabTextColor != null) mCheckText.setTextColor(tabTextColor);
mCheckText.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
mCheckText.setFocusable(false);
addCheckableView(mCheckText);
//添加小红点
mTipView = (TipView) rootView.findViewById(R.id.tipView);
//设置小红点背景颜色
mTipView.setTipBackgroundColor(mTipBackgroundColor);
mTipView.setTextColor(tipTextColor);
//设置整个View
addView(rootView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
setClickable(true);
setSaveEnabled(true);
}




@Override
public boolean isInEditMode() {
return true;
}


public void setTipText(String number) {
mTipView.setText(number);
}


public int getTipCount() {
return mTipCount;
}


public void setTipCount(int count) {
mTipCount = count;
if (count <= 0) mTipView.setVisibility(GONE);
else mTipView.setVisibility(VISIBLE);
if (count > 99) mTipView.setText("N");
else mTipView.setText(count + "");
XLog.i("tipCount"+getTipCount());
}




public interface OnCheckedChangeListener {
void onCheckedChanged(Tab tab, boolean isChecked);
}


@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
if (mCheckChangedListener != null) {
mCheckChangedListener.onCheckedChanged(this, checked);
}


}




@Override
public void onRestoreInstanceState(Parcelable state) {
SaveState saveState = (SaveState) state;
if (saveState.tipCount != -1) {
setTipCount(saveState.tipCount);
}
super.onRestoreInstanceState(saveState.getSuperState());


}


@Override
public Parcelable onSaveInstanceState() {
Parcelable superSave = super.onSaveInstanceState();
SaveState saveState = new SaveState(superSave);
saveState.tipCount = mTipCount;
return saveState;
}


public void setOnCheckChangedListener(OnCheckedChangeListener checkChangedListener) {
mCheckChangedListener = checkChangedListener;
}


public CharSequence getText() {
return mCheckText.getText();
}


static class SaveState extends BaseSavedState {
int tipCount;


public SaveState(Parcel source) {
super(source);
tipCount = source.readInt();
}


public SaveState(Parcelable superSave) {
super(superSave);
}




@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(tipCount);
}


public static final Creator<SaveState> CREATOR = new Creator<SaveState>() {
@Override
public SaveState createFromParcel(Parcel source) {
return new SaveState(source);
}


@Override
public SaveState[] newArray(int size) {
return new SaveState[size];
}
};




}


}



  1. 创建TabHost.java
package me.hangox.tabhost;




import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;




/**
*
* @author 47
* Tab容器
*/
public class TabHost extends LinearLayout {
// holds the checked id; the selection is empty by default
private int mCheckedId = -1;
// tracks children radio buttons checked state


private CheckedStateTracker mChildOnCheckedChangeListener;
// when true, mFragmentTabAdapter discards events
private boolean mProtectFromCheckedChange = false;
private FragmentTabAdapter mFragmentTabAdapter;
private PassThroughHierarchyChangeListener mPassThroughListener;
//restore onAdapter
private Parcelable mAdapterSavedState;
private ClassLoader mAdapterClassLoader;






/**
* {@inheritDoc}
*/
public TabHost(Context context) {
super(context);
setOrientation(HORIZONTAL);
init();
}


@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}


/**
* {@inheritDoc}
*/
public TabHost(Context context, AttributeSet attrs) {
super(context, attrs);
init();


}


@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}


private void init() {
mChildOnCheckedChangeListener = new CheckedStateTracker();
mPassThroughListener = new PassThroughHierarchyChangeListener();
super.setOnHierarchyChangeListener(mPassThroughListener);
}




@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof Tab) {
final Tab tab = (Tab)child;
if (tab.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(child.getId());
}
}


super.addView(child, index, params);
}






/**
* <p>Sets the selection to the radio button whose identifier is passed in
* parameter. Using -1 as the selection identifier clears the selection;
*
* @param id the unique id of the radio button to select in this group
*
* @see #getCheckedTabId()
*/
public void check(int id) {
// don't even bother
if (id != -1  && (id == mCheckedId)) {
return;
}


if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}


if (id != -1) {
setCheckedStateForView(id, true);
}


}


/**
* 设定点击位置
* @param position
*/
public void checkPosition(int position){
if(position < 0 || position >= getChildCount()) return;
check(getChildAt(position).getId());
}


private void setCheckedId(int id ){
mCheckedId = id;
Tab tab = findTabById(mCheckedId);
if (mFragmentTabAdapter != null) {
mFragmentTabAdapter.onCheckedChanged(this, mCheckedId, tab);
}
}


private void setCheckedStateForView(int viewId, boolean checked) {
//我不知道为什么findviewbyid 无法找到这个view
//        View checkedView = findViewById(viewId);
Tab checkedView = findTabById(viewId);
if (checkedView != null) {
checkedView.setChecked(checked);
}
}




/**
* 自己写的findviewbyid,不知道为何findviewbyid无法找到子view
* @param viewId
* @return
*/
private Tab findTabById(int viewId){
for(int i = getChildCount() - 1; i >= 0 ; i -- ){
View child = getChildAt(i);
if(child.getId() == viewId && child instanceof Tab) return (Tab) child;
}
return null;
}


/**
* <p>Returns the identifier of the selected radio button in this group.
* Upon empty selection, the returned value is -1.</p>
*
* @return the unique id of the selected radio button in this group
*
* @see #check(int)
*
* @attr ref android.R.styleable#RadioGroup_checkedButton
*/
public int getCheckedTabId() {
return mCheckedId;
}




/**
* <p>Register a callback to be invoked when the checked radio button
* changes in this group.</p>
*
* @param adapter the callback to call on checked state change
*/
public void setAdapter(FragmentTabAdapter adapter) {
mFragmentTabAdapter = adapter;
if(mCheckedId != - 1){
mFragmentTabAdapter.onRestoreInstanceState(mAdapterSavedState,mAdapterClassLoader);
setCheckedId(mCheckedId);
}
}






@Override
protected Parcelable onSaveInstanceState() {
SavedState savedState = new SavedState(super.onSaveInstanceState());
savedState.checkedId= mCheckedId;
if (mFragmentTabAdapter != null) savedState.adapterState = mFragmentTabAdapter
.onSavedState();
return savedState;
}


@Override
protected void onRestoreInstanceState(Parcelable state) {
if(!(state instanceof SavedState)){
super.onRestoreInstanceState(state);
return;
}
SavedState savedState  = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
if(mFragmentTabAdapter != null){
mFragmentTabAdapter.onRestoreInstanceState(savedState.adapterState,savedState.loader);
check(savedState.checkedId);
}else{
mAdapterSavedState = savedState.adapterState;
mCheckedId = savedState.checkedId;
mAdapterClassLoader = savedState.loader;
//            XLog.i(mCheckedId);
}
}




static class SavedState extends BaseSavedState{
Parcelable adapterState;
int checkedId = -1;
ClassLoader loader;


SavedState(Parcelable superState){
super(superState);
}


public SavedState(Parcel source,ClassLoader classLoader) {
super(source);
if(classLoader == null) classLoader = getClass().getClassLoader();
adapterState = source.readParcelable(classLoader);
checkedId = source.readInt();
loader = classLoader;
}


@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(adapterState, 1);
dest.writeInt(checkedId);
}




public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in,loader);
}


@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
}




private class CheckedStateTracker implements Tab.OnCheckedChangeListener {


@Override
public void onCheckedChanged(Tab tab, boolean isChecked) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
return;
}


if(mCheckedId == tab.getId()) return;


mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;


int id = tab.getId();
setCheckedId(id);
}
}




public Tab getTabAt(int index){
return (Tab) getChildAt(index);
}


/**
* @author 47
* 一个添加和删除view的监听器
*/
private class PassThroughHierarchyChangeListener implements
OnHierarchyChangeListener {
private OnHierarchyChangeListener mOnHierarchyChangeListener;


/**
* {@inheritDoc}
*/
public void onChildViewAdded(View parent, View child) {
if (parent == TabHost.this && child instanceof Tab) {
int id = child.getId();
//如果没有分配ID就给一个ID控件
if (id == View.NO_ID) {
child.setId(((Tab) child).getText().hashCode());
}
//                XLog.i(child.getId());
//do something on Add
Tab tab = (Tab) child;
tab.setOnCheckChangedListener(mChildOnCheckedChangeListener);
//aleady add to parent;
tab.setIndex(TabHost.this.getChildCount() - 1);
//自动改变布局为均等大小
LayoutParams layoutParams  = (LayoutParams) tab.getLayoutParams();
if(layoutParams == null)
layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.weight = 1;
layoutParams.width = 0;
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
tab.requestLayout();
}


if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}


/**
* {@inheritDoc}
*/
public void onChildViewRemoved(View parent, View child) {
if (parent == TabHost.this && child instanceof Tab) {
//do something
Tab tab = (Tab) child;
tab.setOnCheckChangedListener(null);
}


if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
}

TabHost的设计其实很大一部分都是参考RadioGroup源码的

Android A启动到D再回到A的解决方案

问题描述

启动Activity 顺序为A -> B -> C -> D,忽然想从D回退到C(例如场景为A为登陆,BCD都为注册信息),这样我们应该怎么解决

解决方案

  1. B到D都监听onActivityResult ,一层层结束过来。这是一个很笨的方法,但是管用,就是麻烦了点。
  2. 使用EventBus这种方法,B到C都监听结束事件,算是一种1的改良型,不过这里有个致命的问题,EventBus是通过静态变量来存储事件的,应用回收之后再恢复到D,前面的B到C都是还没有注册过事件的,这样的情况下就会出现Bug
  3. 在A中使用SingleTop 属性和在Intent中使用ClearTop属性 ,当启动A的时候会自动清空A之上的Activity,但是也有个问题,使用这个属性的Activity不能使用startActivityForResult 。
  4. 解决办法3 的改良型,我们不在Androidmainfest.xml 中指定SingleTop 属性,我们可以在Intent指定这次的启动为SingleTop 和 ClearTop 这样就不会有不能使用startActivityForResutl 的问题了。

    ###方法4具体代码如下
    java
    Intent intent = new Intent(this,MainActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    intent.putExtra("type","refresh");//你可以自定传递的内容,在onNewIntent 函数可以看到
    startActivity(intent);

Android沉溺模式

什么是沉溺模式?

我们理解的沉溺模式都是指状态栏和ActionBar一样的颜色,但其实在设计师那边并不是这个意思,我们这里讨论就按我们曲解的意思讨论吧。

有什么难度

沉溺模式其实一开始是从iOS那边过来的,Android上实现沉溺模式并没有那么简单,Android Kitkat才开始可以在状态栏下面放内容,Lollipop 才支持真正意义上的修改状态连颜色。
但是可悲的是KitKat目前是市场上占有率最高的系统,如果media/不是在Kitkat这个版本上实现并没有什么意义.

实现方式

要想在KitKat上实现改变系统状态栏颜色是不可能的,我们只能通过曲线救国的方式来实现。KitKat上支持一种模式,把内容放到StateBar下面,我们通过这种方式来实现改变状态栏颜色的功能,但是这个方法有几个弊端。第一,原生的Android系统,StateBar是有渐变黑色的,这个无法去掉。第二,开启这个功能后,需要根部的View 启用fitSystemWindow功能,但是启用这个功能之后,根部View的Padding将不能使用。

问题解决方案

为了对开发者透明,也为了不每次都会麻烦,我们对开启fitSystemWindow 这个功能进行了封装,原理就是在原本的根部View上包裹一个FrameLayout ,然后使用FrameLayout开启fitSystemWindow ,这样开发者就可以想操作平常的页面一样去开发新的页面了。

效果预览

代码实现

package com.hangox.aboutsystembartint;


import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;


import com.readystatesoftware.systembartint.SystemBarTintManager;


/**
* Created With Android Studio
* User hangox
* Date 15/10/20
* Time 下午9:55
* 沉溺状态栏助手
*/
public class SystemBarTintHelper {
private Activity mActivity;
private SystemBarTintManager mTintManager;


public SystemBarTintHelper(Activity activity) {
mActivity = activity;
setUpTranslucentStatus();//开启透明状态栏
mTintManager = new SystemBarTintManager(mActivity);
}


/**
* 开启调用
*/
public void setUp(int color) {
mTintManager.setStatusBarTintEnabled(true);
mTintManager.setStatusBarTintColor(color);
}




/**
* 使用代码启动透明状态栏
*/
@TargetApi(19)
private void setUpTranslucentStatus() {
Window win = mActivity.getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
winParams.flags |= bits;
win.setAttributes(winParams);
}


/**
* 因为如果根部View设置fitSystemWindow = true 后,padding 就会无效,
* 做不到对开发者透明,所以只能在原本的根部View套上一个FrameLayout
* 再对FrameLayout 执行isFitSystem = true这样就不会影响原来的布局
*/
public void handleViewAfterSetContent() {
ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView().findViewById(android.R.id.content);
View rootView = decorView.getChildAt(0);
decorView.removeView(rootView);
View newRootView = generateNewRootView(rootView, null);
newRootView.setFitsSystemWindows(true);
decorView.addView(newRootView);
}




/**
* 生成一个新的根部View
* @param rootView
* @param layoutParams
* @return
*/
public FrameLayout generateNewRootView(View rootView, ViewGroup.LayoutParams layoutParams){
FrameLayout frameLayout = new FrameLayout(mActivity);
if (layoutParams == null) frameLayout.setLayoutParams(rootView.getLayoutParams());
else frameLayout.setLayoutParams(layoutParams);
frameLayout.addView(rootView);
return frameLayout;
}


public static boolean isKitKat() {
return Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT;
}
}



在Activity 的代码

package com.hangox.aboutsystembartint;


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;


/**
* 示范Activity
*/
public class MainActivity extends AppCompatActivity {


protected SystemBarTintHelper mSystemBarTintHelper ;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//透明状态栏只能再kitkat 以上的版本开启,但是Android5.0 之后提供了primaryDark这种东西更改
//状态栏模式,所以只要再kitkat 模式上开启就好了
if (SystemBarTintHelper.isKitKat()) {
mSystemBarTintHelper = new SystemBarTintHelper(this);
//            mSystemBarTintHelper.setSystemBarColor(getResources().getColor(R.color.colorPrimaryDark));
mSystemBarTintHelper.setUp(getResources().getColor(R.color.colorPrimaryDark));
}
setContentView(R.layout.activity_main);
}


@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
if(SystemBarTintHelper.isKitKat()){
mSystemBarTintHelper.handleViewAfterSetContent();
}
}


@Override
public void setContentView(View view) {
if(SystemBarTintHelper.isKitKat()){
view = mSystemBarTintHelper.generateNewRootView(view,null);
}
super.setContentView(view);
}


@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if(SystemBarTintHelper.isKitKat()){
view =  mSystemBarTintHelper.generateNewRootView(view, params);
}
super.setContentView(view, params);
}
}



通过Gradle 配置应用

问题引入

很多时候,我们的应用会在开发的时候使用一个测试的服务器,在发布正式应用的时候改为正式的服务器。

问题解决方案

  1. 把服务器信息写入一个静态类里面,然后统一引用,改的时候统一修改
  2. 把服务器信息写入到一个assets 文件中,然后再修改
  3. 把测试服务器和正式服务器都写到代码里面,通过BuildConfig.DEBUG 这个字段来判断使用哪一个

解决方案评价

1和2其实操作是一样的,2只是加快了编译速度而已。但是还是避免不了要人工修改的这个问题,很容易遗忘或者误操作,改完之后还要测试一下是否改对了
3操作做到了自动化,但是有个问题,就是把无用的信息带到了发布版里面去,这里的无用信息就是测试服务器的地址,既然是发布版,是断然不会用到测试服务器的,每次启动还要做一个判断是在是逼死我这个强迫症

新的方案

新的方案就是第二个方案的第三个方案的结合。第三个方案不好的地方在于把编译期间就可以做的事情放到了运行时候来做,第二个方案的坏处就是每次都要手动修改,很容易出错和遗忘。
第四个方案具体流程是,编译器就把不同的文件放到对应的的assets 目录中,程序运行时再从目录中读取对应的配置文件。

具体实现

我们先让我们的程序可以读取配置文件先


* 创建一个Model ,用来存放ServiceInfomation

     public class MServiceConfig {
@SerializedName("host")
private String mHost;
@SerializedName("port")
private String mPort;
public String getHost() {
return mHost;
}
public void setHost(String host) {
mHost = host;
}
public String getPort() {
return mPort;
}
public void setPort(String port) {
mPort = port;
}
}


  • 在Application 创建一个解析代码(这样做并不是很好,建议具体实现不要在Application添加代码,只是在Application 解析就好)

    public class MyApplication extends Application {
    private MServiceConfig mMServiceConfig;
    @Override
    public void onCreate() {
    super.onCreate();
    mMServiceConfig = new Gson().fromJson(readConfig(),MServiceConfig.class);
    }
    public MServiceConfig getMServiceConfig() {
    return mMServiceConfig;
    }
    private String readConfig(){
    String config = "";
    try {
    InputStream inputStream = getResources().getAssets().open("config_service.json");
    byte[] buffer = new byte[2048];
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    int length = 0 ;
    while ((length = inputStream.read(buffer)) != -1){
    byteArrayOutputStream.write(buffer,0,length);
    }
    config = byteArrayOutputStream.toString();
    inputStream.close();
    byteArrayOutputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    return config;
    }
    }

下面我们可以进入正题了

怎么在编译期间就把不同的配置文件打包进入到assets呢。答案就是通过使用gradle这个编译系统。

在build.gradle 贴入以下代码就好了

//为每一类型都添加task
android.applicationVariants.all { variant ->
//获取当前变种名字
String variantName = variant.name;
//装换第一个字母为大写
variantName = "${variantName.substring(0, 1).toUpperCase()}" +
"${variantName.substring(1, variantName.length())}"
//获取assets目录的位置
def intoPath
android.sourceSets.main.assets.each{ value ->
intoPath = value.source.get(0)
}
//获取mergeAssets任务
project.getTasksByName("merge${variantName}Assets", false).each {
//创建拷贝任务,把configDebug or configRelease 中的文件拷贝到assets中
Task myTask = project.tasks.create(
name: "configApp${variantName}", type: Copy, overwrite: true) {
group = 'build'//分配在build 分组中
from("src/config${variantName}") {
include '*.json'//只是拷贝json文件,不需要可以去掉
}
into intoPath
}
//使mergeAssets 依赖于这个拷贝任务,这样当megeAssets 执行的时候就会先调用myTask
it.dependsOn myTask
}


}

这样就会把src目录下的configDebug或者configRelease导入到相应的Assets 里面了

源代码

Android提交应用市场失败

昨天终于完成了应用的大版本更新,但是交给运营提交应用市场的时候发现提交不上去,只有百度可以提交上去的,360,豌豆荚都提交不上去。最后折腾了很久用旧版aapt检查了APK,发现有个错误说,Laucher Activity 没有 正确的label,但是发现自己的launcher activity 的label 是正确的,最后发现有个Activity label="@null" 删除后果然没有问题了。

RecyclerView 添加Header和fooder

谷歌发布了一个RecyclerView 来代替ListView,然而RecyclerView 并没有ListView header 和 footer.为什么会没有呢,谷歌坑我们??显然不是。
其实ListView 也是“没有”这个功能,我们看看ListView中setAdapter 的代码片段

public void setAdapter(ListAdapter adapter) {
....
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
...
}


我们可以看setAdapter 对是否有headerView 进行了判断,如果有headviewer的话就会对adapter 进行二次封装,所以这就是为什么我说ListView 其实是没有header和footer,这也是为什么设置了adapter之后添加header会出错的问题了,因为adapter是不一样的
既然知道了ListView 是怎么构造头尾部的,我们也可以用同样的办法给Recyclerview 构造一个
因为我使用footer 的原因是要构造一个更多加载,所以我将已构造一个更多加载为示例

**
* Created With Android Studio
* User 47
* Date 15/5/12
* Time 上午10:07
* 一个Load Adapter
*/
private class MoreAdapter extends Adapter<ViewHolder> {


public static final int LAST_ITEM_TYPE = -2333333;
private  Adapter mAdapter;


private AdapterDataObserver observer = new AdapterDataObserver() {
MoreAdapter moreAdapter = MoreAdapter.this;
@Override
public void onChanged() {
notifyDataSetChanged();
}


@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
moreAdapter.notifyItemRangeChanged(positionStart,itemCount);
}


@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
moreAdapter.notifyItemRangeInserted(positionStart,itemCount);
}


@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
moreAdapter.notifyItemRangeRemoved(positionStart,itemCount);
}


@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
moreAdapter.notifyItemMoved(fromPosition,itemCount);
}
};




public void setWrapAdapter(Adapter adapter){
if(adapter == null) return;
if(mAdapter != null) mAdapter.unregisterAdapterDataObserver(observer);
mAdapter = adapter;
mAdapter.registerAdapterDataObserver(observer);


}


public Adapter getWrapAdapter(){
return mAdapter;
}




@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == LAST_ITEM_TYPE)
return  mMoreViewHolder;
return onRealCreateViewHolder(parent, viewType);
}


@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if(position == getRealItemCount()) {
return;
}
onRealBindViewHolder(holder, position);
}






@Override
public int getItemViewType(int position) {
if(position == getRealItemCount()) return LAST_ITEM_TYPE;
return mAdapter.getItemViewType(position);
}

其实整个代码很简单,就是自己封装一个Adapter,然后可以把Adapter放到里面去,让它注册自己的数据观察者,以便到时候可以通知刷新