Dockerfile命令
FROM
格式为 FROM <image>
或 FROM <image>:<tag>
。
第一条指令必须为 FROM 指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。
MAINTAINER
格式为MAINTAINER <name>
,指定维护者信息。
RUN
格式为 RUN <command>
或 RUN ["executable", "param1", "param2"]
。
前者将在 shell 终端中运行命令,即 /bin/sh -c ;后者则使用 exec 执行。指定使用其它终端可以通过第二种
方式实现,例如 RUN ["/bin/bash", "-c", "echo hello"]
。
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。
CMD
支持三种格式
CMD ["executable","param1","param2"]
使用 exec 执行,推荐方式CMD command param1 param2
在 /bin/sh 中执行,提供给需要交互的应用CMD ["param1","param2"]
提供给 ENTRYPOINT 的默认参数
指定启动容器时执行的命令,每个 Dockerfile 只能有一条CMD 命令。如果指定了多条命令,只有最后一条会 被执行。
如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。
EXPOSE
格式为 EXPOSE
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配
一个端口转发到指定的端口。
ENV
格式为 ENV
例如
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz |
tar -xJC /usr/src/postgress && ... ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH`
ADD
格式为 ADD <src> <dest>
该命令将复制指定的
COPY
格式为 COPY <src> <dest>
复制本地主机的
ENTRYPOINT
两种格式:
ENTRYPOINT ["executable", "param1", "param2"]
(没有Shell的时候使用这个)ENTRYPOINT command param1 param2
(shell中执行)
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效。
VOLUME
格式为 VOLUME ["/data"]
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
USER
格式为 USER daemon
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如: roupadd -r postgres && useradd -r -g postgres postgres
。要临时获取管理员权限可以使用 gosu ,而不推荐sudo 。
WORKDIR
格式为 WORKDIR /path/to/workdir
为后续的 RUN
、 CMD
、 ENTRYPOINT
指令配置工作目录。
可以使用多个 WORKDIR
指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c
ONBUILD
格式为 ONBUILD [INSTRUCTION]
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。 例如,Dockerfile 使用如下的内容创建了镜像 image-A
。
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
**使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild **
Docker配置加速
Linux
curl -sSL https://get.daocloud.io /daotools/set_mirror.sh | sh -s http://3cff1e8d.m.daocloud.io
该脚本可以将 --registry-mirror 加入到你的 Docker 配置文件 /etc/default/docker 中。适用于 Ubuntu14.04、Debian、CentOS6 、CentOS7,其他版本可能有细微不同。更多详情请访问文档。
Windows
在桌面右下角状态栏中右键 docker 图标,修改在 Docker Daemon 标签页中的 json ,把下面的地址:
http://3cff1e8d.m.daocloud.io Copy
加到"registry-mirrors"的数组里。点击 Apply
Mac
Docker For Mac
右键点击桌面顶栏的 docker 图标,选择 Preferences ,在 Advanced 标签下的 Registry mirrors 列表中加入下面的镜像地址:
http://3cff1e8d.m.daocloud.io
点击 Apply & Restart 按钮使设置生效。
Swift学习笔记之集合类型
集合分三种,Array,Set,Dictionary。
- Array 像JS的Array ,可以放key.
- Dictionary 对应Java的Map
数组操作
创建空的数组
var ints = [Int]();
print(ints.count);
添加数据
var ints = [Int]();
ints.append(3);
ints += [3];
print(ints);
置空
var ints = [Int]();
ints.append(3);
ints = []; //置空
print(ints);
初始化数组
var ints = Array(repeating: 2, count: 4);//重复2,重复4遍
print(ints);
数组合并
var ints = Array(repeating: 2, count: 4);//重复2,重复4遍
var otherInts = Array(repeating: 3, count: 3);
var together = ints + otherInts;
print(together);
插入某个位置
var ints = Array(repeating: 2, count: 4);
ints.insert(12, at: 1);
print(ints);
移除最后一个元素
var ints = [2,3,4,5];
ints.removeLast();
print(ints);
循环获取数组
var ints = [2,3,4,5];
for myInt in ints{
print(myInt);
}
循环获取数组值包含index
var names = ["Tomas","HaliPoTe"]
for(index,name) in names.enumerated(){
print("index:\(index) name:\(name)");
}
设置数组的值
var ints = [2,3,4,5];
ints[0] = 10086; //设置0位的值
ints[1...2] = [1007,1008,300]; //设置1到2的值,同时把300插入到后面
print(ints);
集合
NOTE
Swift’s Set type is bridged to Foundation’s NSSet class.
For more information about using Set with Foundation and Cocoa, see Using Swift with Cocoa and Objective-C (Swift 3).
基本数据类型例如Int 这些,已经实现了hashable ,如果是自己的类型必须要自己实现hashable ,和Java重写hashvalue方法感觉差不错
操作
//创建一个空的集合
var emptySet = Set<String>();
//创建并同时初始化集合
var myfavorice: Set<String> = ["苹果","荔枝","西瓜"];
//添加元素
myfavorice.insert("李子");
//置空
myfavorice = [];
//根据类型自动匹配创建
var sex:Set = ["男","女"];
//remove
myfavorice.remove("苹果");
//迭代
for name in myfavorice{
print(name);
}
//集合操作
var classA:Set<String> = ["哆啦A梦","静香","小夫"];
var classB:Set<String> = ["哆啦A梦","大熊","小夫"];
//交集
print("交集:\(classA.intersection(classB))");
//对称差
print("对称差:\(classB.symmetricDifference(classA))");
//并集
print("并集:\(classA.union(classB))");
//差集
print("差集:\(classA.subtracting(classB))");
//相等
print(classB == classA);
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱","鸡"]
let cityAnimals: Set = ["够"]
//是不是farm 的子集
print(houseAnimals.isSubset(of: farmAnimals));
//farm的超集是不是house
print(farmAnimals.isSuperset(of: houseAnimals));
//是否没有任何的交集
print(farmAnimals.isDisjoint(with: cityAnimals));
Swift学习笔记之字符和字符串
就是字符串和字符,没什么好说的
字符串操作
循环获取字符
var name = "Tomas";
for letter in name.characters{
print(letter);
}
字符数组创建字符串
let cat : [Character] = ["m","y","c","😂"];
let catStr = String(cat);
print(catStr);
字符插入
let name = "Tomas";
var say = "my name is \(name)";
print(say);
计算字符数
let sayHello = "Hello 大家好,我是梁胖子";
print("say hello lenght:\(sayHello.characters.count)")
下标获取字符
let name = "Tomas!";
print(name[name.startIndex]);
print(name[name.index(before: name.endIndex)]);
print(name[name.index(name.startIndex, offsetBy: 4)])
Swift笔记之操作符
支持大部分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笔记之基础
声明一个变量
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 操作总结

git 操作总结
开源项目收集
View
- Yalantis/Context-Menu.Android
- emojicon
> A library to show emoji in TextView, EditText (like WhatsApp) for Android
fragment 不重叠
写过类似微博这种tab结构的人都知道,使用的是fragment实现,但是fragment有个问题,就是官方其实没有说明白这个东西到底应该怎么用。比如如果简单的使用rplace来切换tab切换fragment会导致每次都会刷新的问题
使用hide和show显示fragment会带来重叠的问题。
为什么会出现重叠的现象?
因为Activity在内存不足的时候被回收了,再次打开的时候恢复了Activity,之前的Fragment还在Activity中,但是你又重新创建了Fragment,并且Add回去了,所以就重叠了。
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 调用。
讲讲代码
- 创建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().
- 创建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];
}
};
}
}
- 创建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都为注册信息),这样我们应该怎么解决
解决方案
- B到D都监听onActivityResult ,一层层结束过来。这是一个很笨的方法,但是管用,就是麻烦了点。
- 使用EventBus这种方法,B到C都监听结束事件,算是一种1的改良型,不过这里有个致命的问题,EventBus是通过静态变量来存储事件的,应用回收之后再恢复到D,前面的B到C都是还没有注册过事件的,这样的情况下就会出现Bug
- 在A中使用SingleTop 属性和在Intent中使用ClearTop属性 ,当启动A的时候会自动清空A之上的Activity,但是也有个问题,使用这个属性的Activity不能使用startActivityForResult 。
- 解决办法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 配置应用
问题引入
很多时候,我们的应用会在开发的时候使用一个测试的服务器,在发布正式应用的时候改为正式的服务器。
问题解决方案
- 把服务器信息写入一个静态类里面,然后统一引用,改的时候统一修改
- 把服务器信息写入到一个assets 文件中,然后再修改
- 把测试服务器和正式服务器都写到代码里面,通过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放到里面去,让它注册自己的数据观察者,以便到时候可以通知刷新
实例化布局注意的事情
很多时候我们会把一个布局实例化之后添加到布局上
例如
View.inflater(context,R.layout.xxx,null);
但是即使是设置了外围布局的LayoutParam ,实例化之后的对象,也是没有LayoutParam的,所以很多时候都是加入布局后,布局给的默认LayoutParam,所以很多时候我们应该在实例化布局后,添加LayoutParam,不然你是不知道加入布局之后会发生什么事情
2016 修正
LayoutInflater.from(context).inflate(R.layout.xx,viewGroup,false);
上面的代码就能把最外层的布局给带入代买里面去了
Android 开发中慎用静态变量
静态变量,在Java中是当类被卸载的时候会被清空,一般情况下被卸载的时候是JVM结束的时候。但是在Android这里,使用的是Dalvik虚拟机,每个应用启动的时候都会实例化一个虚拟机对象,应用结束的时候就会消失,然后静态变量就会被回收了。
常见场景
应用后台的时候,被回收了,当用户恢复应用的时候,静态变量就会被重置为空,就会出现问题.
自定义按钮状态(Custom States)
Custom States
作者详细介绍了怎么自定义按钮的状态(不是按钮状态的响应哦) 原来这东西还可以自定义的
更改Android Studio 默认打开文件方式
提交XLog到JCenter
为了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
最近在写判断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 打包工具
介绍一个很好用的打包工具gradle-packer-plugin
很多时候我们希望给我们的APK命名为:
"appname_buildtype_buildVersion_versionName_buildingTime.apk"
这样格式方便我们给运营让我们可以一眼识别这个是什么时候发布的APK,但是这样的东西如果让我们手动编写肯定会非常麻烦,gradle-packer-plugin就是用来干这个事情的,当然还支持多渠道打包。
应用的使用方法github上都有介绍,在这里我说说这个插件如何放到run上,让我们可以一键打包
注:下面的方法是要按照GitHub的方法配置完成之后才能做的


- 图上第一个圈圈代表你要打包的项目(选择的时候是不能选目录的,只能选build.gradle,把那一部分的路径删除就可以了)
- 添加任务“archiveApkRelease”
- 添加参数,指定需要打包的市场名单 -Pmarket=markets.txt (我的市场名单在根目录)
- 点击+号,添加一个运行前的清理任务(实验证明,没有这个也是可以的) ,配置重复上面,不懂的地方在于添加的任务为"clean" 详情看图二
- 点击确定
- 最后我们就可以点击run来打包应用啦
XLog介绍
XLog
XLog 是一个帮助Android开发打印的调试信息的工具类
- 自动填充TAG(格式为类名+函数名+方法名+行号)
- 无需考虑print string为null的事情
- 一个方法取消所有打印
平常使用 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介绍使用
PhotoCroper
A helper to use system gallery or camera take Photo
used code
fix some problem
-
- 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());
- insert handle
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
cropHelper.handleResult(requestCode,resultCode,data);
}- 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
关于Android菜单上的记录
最近在做分享上一下事情,想遵守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的例子
<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 主题修改
下面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 工作流
场景
项目已经上线,有用户反馈说我们的程序有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分支上
这样就比较好的方式解决了上面这个问题了