47Log

没错,就是我

  • 首页
  • 代码
  • 设计
  • 感悟
  • BUG肥猿瘦
  • 搜索

Swift笔记之操作符

发表于 2016-08-03  |  分类于 代码  |  暂无评论

支持大部分C语言的操作符,但是又有不同

  • “=” 不返回任何值,避免了和==号混淆
  • 提供两个范围操作符(a..<b and a...b)
  • 与Java不同,支持操作符重载

其他地方都和Java或者C没什么不一样

特殊地方

支持元素块比较

(1, "zebra") < (2, "apple")   // true because 1 is less than 2
(3, "apple") < (3, "bird")    // true because 3 is equal to 3, and "apple" is less than "bird"
(4, "dog") == (4, "dog")      // true because 4 is equal to 4, and "dog" is equal to "dog"
阅读全文 »

Swift笔记之基础

发表于 2016-08-02  |  分类于 代码  |  暂无评论

声明一个变量

var name = "我是客服";

申明一个常量

let youName = "不是客服";
youName = "你才是客服"; //将会编译不通过

打印

print("string");
//打印字符串
var name = "tomas";
print("my name is \(name)");

//打印整数
var age = "12";
print("my age is \(age)");

分号

Swift 不一定需要分号,在一行中写两句代码才需要

let name = "cat"; var age = 12;
//不需要分号写法
let name = "cat"
var age = 12

整形

Swift 提供8 ,16,32和64位整形和无符号整形,命名方法和C相似,8位无符号为UInt8,32位为Int32.

获取最大最小值方法

let minUInt = UInt8.min
let maxUInt = UInt8.max

Int & UInt

  • 在32位平台上,Int 就是32位的
  • 在64位平台上,Int 就是64位的

浮点类型

  • Double 相当于64位的浮点类型
  • Float 相当于32位的浮点类型
    *
    ### Swift是类型安全和类型推断的
var age = 12;
age = "Tomas"; //这行是编译不通过的,age 已经推断为Int型,无法赋值String类型

数字样式

  • 十进制 没有前缀
  • 二进制 0b开头
  • 八进制 0o开头
  • 十六进制 0x开头

科学计数法

十进制

  • 1.25e2 -> 1.25 x 102 or 125.0.
  • 1.25e-2 -> 1.25 x 10-2 or 0.0125.

十六进制

  • 0xFp2 -> 15 x 22, or 60.0.
  • 0xFp-2 -> 15 x 2-2, or 3.75.

12.1875的多种写法

let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

还可以增加一些额外的格式来使数字更容易看懂

let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

强行转换

let a = 12.9;
var b: Int = Int(a);

类型别名

就是和C++一样,可以吧Int改为另外一个名字

typealias AudioSample = UInt16;
var maxAmplitudeFound = AudioSample.min;

布尔类型

Swift 有个基本数据类型Bool, 取值true or false

let orangesAreOrange = true
let turnipsAreDelicious = false

元组

装包

let http404Error = (404, "Not Found");
//http404Error 类型就是(Int,String) 类型

拆包

var http404NotFound = (404,"NOT FOUND");
var (code,message) = http404NotFound;
print(code,message);

部分拆包

var (code,_) = http404NotFound;

直接通过下标去访问

print(http404NotFound.0,http404NotFound.1);

还可以直接通过key访问,像Map一样

var http500 = (code : 500,message:"Sever Error");
print(http500.code,http500.message);

optional(可选项)

思考以下代码,convertedNumber 是optional Int 还是 Int

let possibleNumber = "123";
let convertedNumber = Int(possibleNumber);

由于posssibleNumber 并不是一定是可以转换为Int类型的,当不能返回的时候就是nil,所以应该是optional Int 对象。

nil

你可以给一个可选类型设置nil值

var version: Int? = nil;
print(version)

非可选类型是不可以给一个nil值的,只有可选类型才可以

直接定义个可选类型默认就是nil赋值

var version: Int?;
print(version) ;//为nil

和OC上的nil并不是一样的,Swift上的只是实际值不在的时候一个确定的赋值类型,并不仅仅用于对象,所有类型都是可以的

if 语句以及强制解析

判断可选类型中是否有值

if containAValue != nil{
    //do something
}

使用可选类型

if containAValue != nil{
    print("\(containAValue!)");
}

使用"!"强制使用可选类型的时候必须确定部位nil,不然将会出现错误

可选绑定

var name : String? = "test";//name 不为nil时将会执行
if let myName = name{
    print(myName);
}

错误处理

错误处理写法

do {
    try canThrowAnError()
    // no error was thrown
} catch {
    // an error was thrown
}
阅读全文 »

git 操作总结

发表于 2016-06-25  |  分类于 分享  |  暂无评论

Git操作图

阅读全文 »

git 操作总结

发表于 2016-06-25  |  分类于 代码  |  暂无评论

Git操作图

阅读全文 »

开源项目收集

发表于 2016-06-25  |  分类于 分享  |  暂无评论

View

  1. Yalantis/Context-Menu.Android

    

  2. emojicon
    

    > A library to show emoji in TextView, EditText (like WhatsApp) for Android
    


    

    

阅读全文 »

fragment 不重叠

发表于 2016-06-25  |  分类于 代码  |  暂无评论

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

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

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





阅读全文 »

TabHost的编写过程

发表于 2016-06-25  |  分类于 代码  |  暂无评论

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


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


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

  • TabHost 架构
  • 讲讲代码

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的解决方案

发表于 2016-06-25  |  分类于 代码  |  暂无评论

问题描述

启动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沉溺模式

发表于 2016-06-25  |  分类于 代码  |  暂无评论

什么是沉溺模式?

我们理解的沉溺模式都是指状态栏和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 配置应用

发表于 2016-06-25  |  分类于 代码  |  暂无评论

问题引入

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

问题解决方案

  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提交应用市场失败

发表于 2016-06-25  |  分类于 代码  |  暂无评论

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

阅读全文 »

RecyclerView 添加Header和fooder

发表于 2016-06-25  |  分类于 代码, 感悟  |  暂无评论

谷歌发布了一个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放到里面去,让它注册自己的数据观察者,以便到时候可以通知刷新

阅读全文 »

实例化布局注意的事情

发表于 2016-06-25  |  分类于 感悟  |  暂无评论

很多时候我们会把一个布局实例化之后添加到布局上
例如

View.inflater(context,R.layout.xxx,null);

但是即使是设置了外围布局的LayoutParam ,实例化之后的对象,也是没有LayoutParam的,所以很多时候都是加入布局后,布局给的默认LayoutParam,所以很多时候我们应该在实例化布局后,添加LayoutParam,不然你是不知道加入布局之后会发生什么事情

2016 修正

LayoutInflater.from(context).inflate(R.layout.xx,viewGroup,false);

上面的代码就能把最外层的布局给带入代买里面去了

阅读全文 »

Android 开发中慎用静态变量

发表于 2016-06-25  |  分类于 感悟  |  暂无评论

静态变量,在Java中是当类被卸载的时候会被清空,一般情况下被卸载的时候是JVM结束的时候。但是在Android这里,使用的是Dalvik虚拟机,每个应用启动的时候都会实例化一个虚拟机对象,应用结束的时候就会消失,然后静态变量就会被回收了。



常见场景

应用后台的时候,被回收了,当用户恢复应用的时候,静态变量就会被重置为空,就会出现问题.

阅读全文 »

自定义按钮状态(Custom States)

发表于 2016-06-25  |  分类于 分享  |  暂无评论

Custom States
作者详细介绍了怎么自定义按钮的状态(不是按钮状态的响应哦) 原来这东西还可以自定义的

阅读全文 »

更改Android Studio 默认打开文件方式

发表于 2016-06-25  |  分类于 代码  |  暂无评论

有时候要更改Android Studio默认打开方式 setting 里面找file types 就可以了
具体详情看这里

阅读全文 »

提交XLog到JCenter

发表于 2016-06-25  |  分类于 代码  |  暂无评论

为了XLog的使用方便,我决定把XLog提交到jcenter
参考了这篇文章

将自己的开源项目提交到JCENTER


这篇文章是可行的,需要修改的地方是把项目的build.gradle 修改为以下内容

// Top-level build file where you can add configuration options common to all sub-projects/modules.


buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.2'
classpath 'com.github.dcendents:android-maven-plugin:1.2'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}




}


allprojects {
repositories {
jcenter()
}


}



需要把build:gradle:1.1.0 改为 1.1.2 ,因为1.1.0有bug,会出现错误

阅读全文 »

分享一段小代码canScrollList

发表于 2016-06-25  |  分类于 代码  |  暂无评论

最近在写判断ListView是否可以滚动的代码,忽然发现一个函数
“canScrollList“
它的代码是这样的

/**
* Check if the items in the list can be scrolled in a certain direction.
*
* @param direction Negative to check scrolling up, positive to check
*            scrolling down.
* @return true if the list can be scrolled in the specified direction,
*         false otherwise.
* @see #scrollListBy(int)
*/
public boolean canScrollList(int direction) {
final int childCount = getChildCount();
if (childCount == 0) {
return false;
}


final int firstPosition = mFirstPosition;
final Rect listPadding = mListPadding;
if (direction > 0) {
final int lastBottom = getChildAt(childCount - 1).getBottom();
final int lastPosition = firstPosition + childCount;
return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
} else {
final int firstTop = getChildAt(0).getTop();
return firstPosition > 0 || firstTop < listPadding.top;
}
}



看到这里大家都明白了,这个代码虽然是API19才能用,但是是可以提取出来兼容到低版本的
于是,我就做了这样一个兼容的方法



/**
* 判断是否可以滚动的兼容函数
* @param listView
* @param direction 方向,大于0就是向下的方向
* @return
*/
public static boolean canScrollList(AbsListView listView, int direction) {
if (Build.VERSION.SDK_INT >= 19) return listView.canScrollList(direction);
else {
final int childCount = listView.getChildCount();
if (childCount == 0) {//列表中没有一个子对象的时候,当然是不能滚动啦
return false;
}
final int firstPosition = listView.getFirstVisiblePosition();
if (direction > 0) {//判断方向,大于零的就是表示判断能否往下滚动
final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
final int lastPosition = firstPosition + childCount;
//最右一个item的位置小宇总位数的时候可以滚动
// 最后一个item的底部高度如果比减去padding之后的
// listview底部还要高的那就是在屏幕外,就是可以滚动的
return lastPosition < listView.getCount() || lastBottom > listView.getHeight() - listView.getListPaddingBottom();
} else {
final int firstTop = listView.getChildAt(0).getTop();
return firstPosition > 0 || firstTop < listView.getListPaddingTop();
}
}
}

注意这个东西只能在item 等高的时候判断

阅读全文 »

gradle 打包工具

发表于 2016-06-25  |  分类于 代码  |  暂无评论

介绍一个很好用的打包工具gradle-packer-plugin



很多时候我们希望给我们的APK命名为:


"appname_buildtype_buildVersion_versionName_buildingTime.apk"

这样格式方便我们给运营让我们可以一眼识别这个是什么时候发布的APK,但是这样的东西如果让我们手动编写肯定会非常麻烦,gradle-packer-plugin就是用来干这个事情的,当然还支持多渠道打包。


应用的使用方法github上都有介绍,在这里我说说这个插件如何放到run上,让我们可以一键打包

注:下面的方法是要按照GitHub的方法配置完成之后才能做的

图一
图二



  1. 图上第一个圈圈代表你要打包的项目(选择的时候是不能选目录的,只能选build.gradle,把那一部分的路径删除就可以了)
  2. 添加任务“archiveApkRelease”
  3. 添加参数,指定需要打包的市场名单 -Pmarket=markets.txt (我的市场名单在根目录)
  4. 点击+号,添加一个运行前的清理任务(实验证明,没有这个也是可以的) ,配置重复上面,不懂的地方在于添加的任务为"clean" 详情看图二
  5. 点击确定
  6. 最后我们就可以点击run来打包应用啦图三
阅读全文 »

XLog介绍

发表于 2016-06-25  |  分类于 代码  |  暂无评论

XLog

XLog 是一个帮助Android开发打印的调试信息的工具类

  1. 自动填充TAG(格式为类名+函数名+方法名+行号)
  2. 无需考虑print string为null的事情
  3. 一个方法取消所有打印
    

    平常使用 Log
    Log.i(TAG,print string);

我们需要构建TAG,并且还要防止 print string 为null 这种情况


在xlog,不需要考虑这些

    XLog.i(print object);

不需要tag ,自动默认的tag就是当前调用的类和行数,打印的是object.toString()内容,不需要把基本类型弄成
string,自动判断是否为空,为空时,会打印报空的问题。

    XLog.setLogState(false);

关闭所有的debug
也可以单独关闭每一个

    XLog.allowI = false;




Thanks for

xutils 一个开源框架,xLog参考了其中LogUtils大部分的思路,改进了流程





阅读全文 »

PhotoCroper介绍使用

发表于 2016-06-25  |  分类于 代码  |  暂无评论

PhotoCroper

A helper to use system gallery or camera take Photo
used code
fix some problem

  1. 


    • can not save crop pitcure
    • can not picker some photo from gallery
    • some api review
      

      # How to use
    • new helper and set CropParam
    CropHelper cropHelper = new CropHelper(this);
    cropHelper.setCropParams(new CropParams());
    
    1. insert handle
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    cropHelper.handleResult(requestCode,resultCode,data);
    }
    1. handle result
     cropHelper.setCropListener(new CropListener() {
    @Override
    public void onPhotoCropped(Uri uri) {
    Log.d(TAG, "Crop Uri in path: " + uri.getPath());
    Toast.makeText(TestActivity.this, "Photo cropped!", Toast.LENGTH_LONG).show();
    mImageView.setImageBitmap(cropHelper.getCropBitmap());
    }
    

    @Override
    public void onCropCancel() {
    Toast.makeText(TestActivity.this,"cancel",Toast.LENGTH_SHORT).show();
    }
    

    @Override
    public void onCropFailed(String message) {
    Toast.makeText(TestActivity.this,message+"",Toast.LENGTH_SHORT).show();
    }
    });

    


    Thanks for

  2. Android大图片裁剪终极解决方案(上:原理分析)

  3. Code
    

    


阅读全文 »

关于Android菜单上的记录

发表于 2016-06-25  |  分类于 代码  |  暂无评论



最近在做分享上一下事情,想遵守Android Design 规范做一个分享的菜单但是遇到一些问题


1. Android上ShareProvider 不支持捕获分享(也许是我没找到),于是就不能针对某个分享项做优化
2. 抛弃ShareProvider使用,单纯的使用Android的菜单分享选项,这样虽然解决了不能捕获的问题,但是也是带了一个问题,就是无法用比较少的代码来完成分享。(发现弹出对话框的分享方式是最简单省事的)

最后权衡了知识和利弊之后只能做个简单的分享类,在CreateMenu 的时候addSubMenu ,
然后在OnMenuOptionSelected() handle 操作就可以了。


代码如下

package com.changheng.app.ui;


import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;


import com.changheng.app.R;


import cn.sharesdk.framework.Platform;
import cn.sharesdk.framework.ShareSDK;
import cn.sharesdk.wechat.favorite.WechatFavorite;
import cn.sharesdk.wechat.friends.Wechat;
import cn.sharesdk.wechat.moments.WechatMoments;


/**
* Created With Android Studio
* User 47
* Date 2015/2/13
* Time 14:29
* 分享助手
*/
public class ShareMenuHelper  {
private Context mContext;
private SubMenu mSubMenu;
private static ShareItem[] SHARE_ITEM = {
new ShareItem(R.drawable.logo_wechat,
R.string.wechat,
ShareSDK.getPlatform(Wechat.NAME)),
new ShareItem(R.drawable.logo_wechatfavorite,
R.string.wechatfavorite,
ShareSDK.getPlatform(WechatFavorite.NAME)),
new ShareItem(R.drawable.logo_wechatmoments,
R.string.wechatmoments,
ShareSDK.getPlatform(WechatMoments.NAME))
};


public ShareMenuHelper(Context context,SubMenu subMenu){
mContext = context;
mSubMenu = subMenu;
}


public void init(){
int i = 0;
for (ShareItem shareItem : SHARE_ITEM){
SubMenu subMenu = mSubMenu.addSubMenu(
Menu.NONE,shareItem.id = i++,Menu.NONE,
shareItem.titleRes);
subMenu.setIcon(shareItem.iconId);
}
}




public void handlerMenuSelected(MenuItem menuItem, Platform.ShareParams shareParams){
int id = menuItem.getItemId();
for (ShareItem shareItem : SHARE_ITEM) {
if (id == shareItem.id) doShare(shareItem,shareParams);
}
}


private void doShare(ShareItem shareItem, Platform.ShareParams shareParams) {
shareItem.platform.share(shareParams);
}


public static class ShareItem{
public int id;
public int iconId;
public int titleRes;
public Platform platform;


public ShareItem( int iconId, int titleRes, Platform platform) {
this.iconId = iconId;
this.titleRes = titleRes;
this.platform = platform;
}
}


}





这个东西主要比较纠结的地方在于,如何获取submenu。找了好久才发现代码是这样的

menuItem.getSubMenu();
阅读全文 »

一个使用Toolbar的例子

发表于 2016-06-25  |  分类于 代码  |  暂无评论
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:background="?attr/colorPrimary"
app:titleTextAppearance="@style/MyTitleTextStyle"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"/>

about apptheme

app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"

more
这个使用的主题必须要和Actionbar使用的主题对应,比如的我用的Light.DarkAction.那么这个主题必须也要是这个,不然会连带ActionBar那边的按钮也会出现问题


about titleTextAppearance

app:titleTextAppearance="@style/MyTitleTextStyle"
阅读全文 »

ActionBar 主题修改

发表于 2016-06-25  |  分类于 代码  |  暂无评论

下面XML演示如何在v21 appcompact上修改Actionbar title,颜色,字体颜色

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#FF3333</item>
<!--   darker variant for the status bar and contextual app bars -->
<item name="colorPrimaryDark">#FF3333</item>
<!--   theme UI controls like checkboxes and text fields -->
<item name="colorAccent">#FFF</item>
</style>


<!--<style name="pallte_btn">-->
<!--<item name="android:layout_width">60dp</item>-->
<!--<item name="android:layout_height">60dp</item>-->
<!--</style>-->


<!--<style name="tabStyle">-->
<!--</style>-->


<style name="TestTheme" parent="AppTheme">
<item name="actionBarStyle">@style/actionBarStyle</item>
<item name="actionMenuTextAppearance">@style/myActionMenuTextAppearance</item>
</style>


<style name="myActionMenuTextAppearance" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Menu">
<item name="android:textSize">20sp</item>
<item name="android:shadowRadius">4</item>
<item name="android:shadowColor">#000</item>
<item name="android:shadowDx">2</item>
<item name="android:shadowDy">2</item>
</style>


<style name="actionBarStyle" parent="@style/Widget.AppCompat.Light.ActionBar.Solid">
<item name="titleTextStyle">@style/MyTitleTextStyle</item>
</style>


<style name="MyTitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
<item name="android:shadowRadius">4</item>
<item name="android:shadowColor">#000</item>
<item name="android:shadowDx">2</item>
<item name="android:shadowDy">2</item>
</style>
阅读全文 »

Git 工作流

发表于 2016-06-16  |  分类于 感悟  |  暂无评论

场景

项目已经上线,有用户反馈说我们的程序有bug。同时,产品狗扔下了两个发布时间不一样的需求修复,拍照功能和美颜功能,拍照功能要下一版本发布,美颜功能要下下版本发布

问题

如何修复Bug同时,不影响新功能的开发,并且把两次的需求隔离开来.

解决方案

建立完善的工作流

项目初始化的时候我们新建三个分支,分别是release ,develop和master。同时,项目发布的时候我们要建立tag,通过tag我们找回对应版本的代码。

release 分支

保存可以发布的或者已经发布的代码分支

master 分支

保存着开发好测试过的代码,每次develop上开发好的功能都可以合并到master分支

develop 分支

我们的开发分支,开发过程中分享的代码

有这三个分支的前提下我们就可以从容的应对需求了。

修复线上的bug

从v1.0.0(假设出现问题的就是v1.0.0版本) tag 中 checkout出新的分支,hotfix_xxx, 修复好之后直接合并回去发布

开发拍照功能

直接在develop上开发,开发完成后合并到master上,测试过后就可以合并到release上了

开发美颜功能

从master中新建一个分支,叫feature_pretty ,在这个分支上同步开发,功能要上线的时候再合并到release分支上

这样就比较好的方式解决了上面这个问题了

参考

  • Git Pro
阅读全文 »

Git快速入门

发表于 2016-06-14  |  分类于 代码  |  暂无评论

初始化项目

直接clone已经有的项目

git clone https://github.com/hangox/git-use-demo.git

或者初始化本地项目

移动到项目的地址

cd /Users/hangox/Desktop/AndroidWorkspace/GitUseDemo

再敲入

git init

这个时候敲入git status 就会看到

On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
.idea/
app/
build.gradle
gradle.properties
gradle/
gradlew
gradlew.bat
settings.gradle
nothing added to commit but untracked files present (use "git add" to track)

这是时候就会表示成功了

设置代码提交者信息

如果不设置代码提交者信息当然也可以提交,但是谁不希望提交上去的代码有个有个牛逼的名字呢😈

设置名字:

git config user.name "NiuBi"

设置邮箱:(出问题了人家可以找到你😂)

git config user.email "hangox.liang@gmail.com"

如果你想所有项目都是应用这个名字和邮箱只需要加上参数-- global
比如:

git config --global user.name "NiuBi"
git config user.email "hangox.liang@gmail.com"

要想查看现在的config信息可以这样

git config --list

你就会看到

user.name=47
user.email=liang.hanguang93@gmail.com
core.autocrlf=input
core.excludesfile=/Users/hangox/.gitignore_global
core.ignorecase=false
difftool.sourcetree.cmd=opendiff "$LOCAL" "$REMOTE"
difftool.sourcetree.path=
mergetool.sourcetree.cmd=/Applications/SourceTree.app/Contents/Resources/opendiff-w.sh "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"
mergetool.sourcetree.trustexitcode=true
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
user.name=NiuBi

如果你发现了两个user.name和user.email ,这是其中一个是global的,一个是项目的而已

当然git config 还有很多可以设置的,包括合并工具什么的,自己挖掘吧!

编写.gitignore

什么是.gitignore

.gitignore 就好像它的名字一样

告诉 git忽略一些不需要的文件

.gitignore 编写规则

项目文件结构是这样的

.
├── GitUseDemo.iml
├── app
│   ├── app.iml
│   ├── build
│   ├── build.gradle
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
├── build
│   └── generated
├── build.gradle
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

我想要忽略build文件夹,.iml 结尾文件和local.properties

#忽略build 文件夹
/build
#忽略local.properties 文件
local.properties
#忽略.xml 结尾的文件,使用正则
*.iml
#不忽略GitUseDemo.xml,这里只是为了演示,其实应该忽略的
!GitUseDemo.iml
#gradle 缓存文件,不需要提交
.gradle
#idea 的配置文件,不需要提交
.idea


这个时候我们敲入git status

On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
GitUseDemo.iml
app/
build.gradle
gradle.properties
gradle/
gradlew
gradlew.bat
settings.gradle
nothing added to commit but untracked files present (use "git add" to track)

可以看到不需要的东西都被忽略了

添加需要提交的文件到暂存区

添加当前文件夹所有文件

git add .

移除追踪的文件

##去掉--cached就是同时删除文件
git rm --cached build.gradle

使用简单的匹配添加文件

添加后缀为.gradle的文件

git add *.gradle

提交

简单的提交方法

git commit -m 'first commit'

如果你写的东西比较长,可以这样

git commit 

这样将会出现一个vim 编辑器来编写,像这样


推送到远端

以上我们做的都这是发生在本地(这也是为什么Git快的原因了),如果需要和小伙伴们合作的话,就需要推送到远端了。

如果你是clone下来的可以跳过以下这一步了

  • 添加远端
# 添加远端,origin 只是一个名字而已,只是默认都是叫这个
git remote add origin https://github.com/hangox/git-use-demo.git
# 查看远端信息
git remote -v
  • 更新以下本地的代码(每次同步之前建议这样做,虽然我老是忘记/(ㄒoㄒ)/~~)
# pull 其实是包含了两步操作,从remote 中fetch(拿来)代码,和当前分支(也就是master) 
# merge(合并),如果你们公司使用的是rebase的话,就不能这样干了。但是大部分情况下都是
# 使用merge的
git pull origin master
  • 上传代码
# git push <remote-name> <remote-branch-name>
git push origin master

等待片刻,代码就上传完毕,你就可以和小伙伴们愉快的玩耍了O(∩_∩)O~~

参考

  • Git Pro
阅读全文 »

Android App网络通信安全

发表于 2016-06-13  |  分类于 代码  |  暂无评论




前提

没有100%的安全,我们做的只是提高破解的成本,当成本比破解得到的金钱高的时候,自然没有人愿意破解了



场景

我们的接口协议被破,破解者通过程序直接刷我们的新增用户,通过邀请机制获取积分。



问题引出

如何保证和接口通信的就是我们的客户端,而不是别人,或者更深的一层,如何保证客户端和服务器通信的就是彼此,没有第三方。



根据问题得出要做到以下几点

  1. 数据不被篡改
  2. 数据只有双方才能看懂
  3. 数据具有时效性

如何保证数据不被篡改

加上数据完整性的校验签名(前提是只有客户端才能生成这个检验签名)

如何使数据只有双方才能看懂

使用加密算法(前提是只有客户端才能生成校验签名)


能做到以上两点,网络通信就没有基本问题了。
但是,以上两点都有个非常重要的前提,也是一个非常困难的前提

只有客户端能生成一些东西

数据具有时效性

加入加密过的时间戳(前提是只有客户端才能生成这个时间戳)

如何保证客户端生成一些东西

使用C写生成的逻辑,不使用Java

Java是非常容易反编译的语言,即使混淆了,一些常量也是无法混淆的,所以当反编译的时候非常容易通过搜索来找到对应的常量。但是C不一样,C写的东西反编译后是汇编代码,汇编代码并不是多少人可以看得懂的,一定程度上提高了破解的成本。

对调用者进行校验

Android的so库,如果不对使用者进行校验,使用的人可以新建一个一样包名的应用,然后调用我们的so库,一样可以知道对应的输入输出。我们这里在C库里做了对签名的校验,如果发现签名不是我们的,就会用另外一条密钥对数据进行加密。

总结

要想网络协议不被破解,App必须要能够有一个黑匣子一样的东西,让人无法破解,最常用的办法就是把安全逻辑的代码写入到C库中,并对C库进行校验。

延伸和拓展

  1. 对C库代码进行混淆,提高反编译难度
  2. 没有100%的安全,做好行为监控才是王道,如果一个用户压根就没有点开过这个页面,却收到了这个页面的请求,这点足够怀疑这个请求的来源了。
阅读全文 »

Android 自定义按钮状态

发表于 2016-02-12  |  分类于 代码  |  暂无评论

需求描述:

最近做个项目,有一个按钮,有下载,安装,点击,安装中,下载中和使用这六个不同的状态,每个状态对应的按钮颜色也是不一样的,加上按下去的变色,大概有12种状态。

解决方案:

  • 6种状态对应不一样的background,设置为不同的状态的时候,换上对应的背景。
  • 自定义按钮状态

选择方案:

其实第二种和第一种的本质区别就是把所有的状态的文件写到一个xml里,但是更优雅,果断使用第二种

方案实现:

  • 定义自己的状态
  • 重写控件的onCreateDrawableState(int extraSpace) 告诉系统你有多少种状态

就这两个步骤,非常简单

定义自己的状态
   <declare-styleable name="ColorStateButton">
<attr name="state_download" format="boolean"/>
<attr name="state_downloading" format="boolean"/>
<attr name="state_install" format="boolean"/>
<attr name="state_installing" format="boolean"/>
<attr name="state_open" format="boolean"/>
</declare-styleable>

其实我想试试可不可以直接用数字类型的,当时时间比较紧急,就没试了,有兴趣的可以试试

重写onCreateDrawableState
 @Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mColorStatus != null && mColorStatus.getMergeState() != null && isEnabled()) {
final int[] drawableStatus = super.onCreateDrawableState(extraSpace + mColorStatus.getMergeState().length);//告诉系统你要添加拓展的状态数量
//告诉系统你要合并的状态
mergeDrawableStates(drawableStatus, mColorStatus.getMergeState());
//            printList(drawableStatus);
return drawableStatus;
}¡
return super.onCreateDrawableState(extraSpace);
}

详细代码:

按钮的background
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- 需要下载状态-->
<item
android:drawable="@drawable/ic_color_button_download_normal"
android:state_pressed="false"
app:state_download="true"/>
<item
android:drawable="@drawable/ic_color_button_download_pressed"
android:state_pressed="true"
app:state_download="true"/>
<!--需要安装状态-->
<item
android:drawable="@drawable/ic_color_button_install"
android:state_pressed="false"
app:state_install="true"/>
<item
android:drawable="@drawable/ic_color_button_install_pressed"
android:state_pressed="true"
app:state_install="true"/>
<!--按钮不可用-->
<item
android:drawable="@drawable/ic_color_button_disable"
android:state_enabled="false"/>
<item
android:drawable="@drawable/ic_color_button_normal_pressed"
android:state_pressed="true"/>
<!--正常状态-->
<item
android:drawable="@drawable/ic_color_button_normal"/>
</selector>
按钮的源码
package com.bingwish.bibao.ui.fragment;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.StringRes;
import android.util.AttributeSet;
import android.widget.Button;
import com.bingwish.bibao.R;
import com.hangox.xlog.XLog;
/**
* Created With Android Studio
* User hangox
* Date 16/1/11
* Time 下午8:58
* 下载显示按钮
*/
public class ColorStateButton extends Button {
public static final int[] STATUS_DOWNLOAD = {R.attr.state_download};
public static final int[] STATUS_INSTALL = {R.attr.state_install};
private ColorStatus mColorStatus = ColorStatus.DOWNLOAD;
public ColorStateButton(Context context) {
super(context);
init(context);
}
public ColorStateButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ColorStateButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ColorStateButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
setColorState(ColorStatus.DOWNLOAD);
}
/***
* 设置颜色类型
*
* @param colorState
*/
public void setColorState(ColorStatus colorState) {
setText(colorState.getButtonTextRes());
switch (colorState) {
case DOWNLOADING:
case INSTALLING:
setEnabled(false);
return;
}
setEnabled(true);
mColorStatus = colorState;
refreshDrawableState();
invalidate();
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mColorStatus != null && mColorStatus.getMergeState() != null && isEnabled()) {
final int[] drawableStatus = super.onCreateDrawableState(extraSpace + mColorStatus.getMergeState().length);
mergeDrawableStates(drawableStatus, mColorStatus.getMergeState());
//            printList(drawableStatus);
return drawableStatus;
}
return super.onCreateDrawableState(extraSpace);
}
private void printList(int[] ints) {
StringBuilder builder = new StringBuilder();
for (int state : ints) {
builder.append(state).append("#");
}
XLog.i(builder.toString());
}
public enum ColorStatus {
DOWNLOAD(STATUS_DOWNLOAD, R.string.state_download),
CLICK(STATUS_INSTALL, R.string.state_click),
OPEN(null, R.string.state_open),
DOWNLOADING(null, R.string.state_downloading),
INSTALL(STATUS_INSTALL, R.string.state_install),
INSTALLING(null, R.string.state_installing);
int[] mergeState;
@StringRes
int mButtonTextRes;
ColorStatus(int[] statusDownload, @StringRes int buttonTextRes) {
mergeState = statusDownload;
mButtonTextRes = buttonTextRes;
}
public int getButtonTextRes() {
return mButtonTextRes;
}
public int[] getMergeState() {
return mergeState;
}
}
}
阅读全文 »

Sock代理转换为http 代理

发表于 2016-02-10  |  分类于 代码  |  暂无评论

为了应对日益强大的防火墙,大部分人开始专用ShadowSock 作为代理,但是ShadowSock并不支持http 代理,而很多软件非常依赖于http 代理,比如Android SDK ,终端等,所以我们需要Privoxy

以下以Mac系统为例演示安装过程

安装
brew install proxivy

如果你看到这个了

表示你已经安装成功了

配置

配置其实很简单,上面的截图已经告诉你配置文件在哪里,就是/usr/local/etc/privoxy/的config 文件
敲入

vi /usr/local/etc/privoxy/config

在最下面加入

# 监听端口为8118,如果需要其他设备访问,则需要将ip改为路由器的IP 192.168.1.1 或 0.0.0.0 或者直接 :8118
listen-address  0.0.0.0:8118 #如果要使用默认配置,可以不需要这行
forward-socks5 / 127.0.0.1:1080 .
#forward-socks5 表示监听sock5 ,127.0.0.1:1080是本地shadowsock 地址, "." 表示监听所有的URL

配置完成后如图


启动

还记得安装成功后的截图不?

这是直接运行,但是关机后就没了

privoxy /usr/local/etc/privoxy/config

下面是加入系统启动项

ln -sfv /usr/local/opt/privoxy/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.privoxy.plist

如果你选择了其中一个敲入,并没有任何提示,那就是表示成功了

验证
  • 首先验证这个进程是否已经启动了
ps aux  | grep privoxy

截图如下:


  • 验证provixy 是否在监听8118这个端口
netstat -an | grep 8118

结果如下:

这样就是表示正在监听8118端口了

阅读全文 »

Linux 笔记

发表于 2016-02-10  |  分类于 代码  |  暂无评论

内核查看

uname -a

上传或者下载文件

#下载
scp -r -P {SSH端口号} {用户名}@{主机IP地址}:{主机文件目录}   {本地文件目录}
#上传
scp -r -P {SSH端口号}  {本地文件目录} {用户名}@{主机IP地址}:{主机文件目录}

查看当前的IP地址

ip addr show

开启某个服务开机启动

sudo chkconfig [服务名] on
sudo chkconfig docker on

Centos7 添加服务器端口

firewall-cmd --zone=public --add-port=80/tcp --permanent

Firewall 移除port

firewall-cmd --zone=public  --remove-port=19999/tcp --permanent

关闭SELinux

修改/etc/selinux/config 文件
将SELINUX=enforcing改为SELINUX=disabled

添加环境变量

不要直接修改/etc/profile 文件,把需要执行的代码加入到xx.sh 文件中,移动到/etc/profile.d/中就好了

阅读全文 »

文章导航

1 … 4 5
avatar

hangox

150 文章
6 分类
47 标签
RSS
友情链接
© 2019 - 2023 hangox
Powered by WordPress
Theme NexT WP