Android开发笔记

本文用于记录开发中遇到的小问题进行记录,方便查阅

获取scrollview滚动的距离

商品详情页需要渐变

1
2
3
4
5
6
7
8
9
10
11
scrollView.setOnTouchListener(this);
@SuppressLint("NewApi")
@Override
public boolean onTouch(View v, MotionEvent event) {

if (event.getAction() == MotionEvent.ACTION_MOVE) {
// 可以监听到ScrollView的滚动事件
System.out.println(scrollView.getScrollY());
}
return false;
}

RecyclerView通过getItemViewType(int position)得到不同的布局

用处广泛

adapter的方法
getItemViewType 返回的是有参数position所决定的的view的id
getViewTypeCount 方法返回的是 你有几种样式 返回时是 int类型 ,顾名思义,就是返回不同布局的数目

获取子item的类型 获取类型的数量 这里是根据字段Mr_type来确定的,json数据里面是根据这个字段来确定消费记录的类型的。总之,在为item设置不同的布局的时候肯定有一个标记用来区分不同的item,你可以用这个作为判断的标记,来设置不同的type。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public int getItemViewType(int position) {
// TODO Auto-generated method stub
if ("0".equals(dataList.get(position).getMr_type())) {
return TYPE_COMSUM;// 消费类型
} else if ("1".equals(dataList.get(position).getMr_type())) {
return TYPE_CHARGE;// 充值类型
} else {
return 100;
}
}

@Override
public int getViewTypeCount() {
return TYPE_COUNT;
}

getItemViewType和getViewTypeCount方法,看名字就知道功能了,一个是获取该项的类型,另一个是获取类型的数量。
所以,使用很简单,第一步,在自定义的Adapter里继承这两个方法,并实现。getItemViewType返回的值不能是自定义的,必须从0开始增长。
第二步,在getView方法里,根据getItemViewType返回的类型,分别inflate对应的xml,


判断RecycleView滑动到最后一个

加载更多以及触发一些事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
       RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycle);
manager = new GridLayoutManager(this, 1, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(new recycleAdapter(MainActivity.this));
//recycleview滑动到底部
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_IDLE) {//当前的recycleView不滑动(滑动已经停止时)
int lastVisiblePosition = manager.findLastVisibleItemPosition();//最后一个可见的位置
if(lastVisiblePosition >= manager.getItemCount() - 1){//如果是最后一个位置就是滑到底部了
System.out.println("滑到底部");
}
}
}
});

值得注意的是如果使用StaggeredGridLayoutManager等Manager,那么获取最后一个position就要像下面这样才可以

int[] visibleItems = mLayoutManager.findLastVisibleItemPositions(null);
int lastitem = Math.max(visibleItems[0],visibleItems[1]);

拓展知识:

* The RecyclerView is not currently scrolling.
 * 当前的recycleView不滑动(滑动已经停止时)
 */
public static final int SCROLL_STATE_IDLE = 0;

/**
 * The RecyclerView is currently being dragged by outside input such as user touch input.
 * 当前的recycleView被拖动滑动
 */
public static final int SCROLL_STATE_DRAGGING = 1;

/**
 * The RecyclerView is currently animating to a final position while not under
 * outside control.
 * 当前的recycleView在滚动到某个位置的动画过程,但没有被触摸滚动.调用 scrollToPosition(int) 应该会触发这个状态
 */
public static final int SCROLL_STATE_SETTLING = 2;

横向滑动加载更多的RecycleView

首页抢购等应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* User: Daidingkang(ddk19941017@Gmail.com)
* Date: 2016-07-02
* Time: 10:42
* FIXME
* 横向滑动加载更多的RecycleView
*/
public class horizontalRecycleView extends RecyclerView {
GridLayoutManager manager;
boolean lastPosition;
Context context;

public horizontalRecycleView(Context context) {
super(context);
init(context);
}


public horizontalRecycleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}

float xMove = 0;
float x = 0;

@Override
public boolean onTouchEvent(MotionEvent e) {

switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
if (lastPosition) {
x = e.getX();
}
break;
case MotionEvent.ACTION_MOVE:
xMove = e.getX();
break;
case MotionEvent.ACTION_UP:
if (lastPosition) {
float xDistance = x - xMove;
if ((xDistance) 200) {//可以判断滑动距离,而且通过正负判断为左滑还是右滑
Toast.makeText(context, "触发横向加载更多", Toast.LENGTH_SHORT).show();
}
}
break;
}

return super.onTouchEvent(e);
}

private void init(final Context context) {
this.context = context;
manager = new GridLayoutManager(context, 1, LinearLayoutManager.HORIZONTAL, false);
setLayoutManager(manager);

addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {//当前的recycleView不滑动(滑动已经停止时)
int lastVisiblePosition = manager.findLastVisibleItemPosition();//最后一个可见的位置
if (lastVisiblePosition >= manager.getItemCount() - 1) {//如果是最后一个位置就是滑到底部了
lastPosition = true;
} else {
if (lastPosition) {
lastPosition = false;
}
}
}
}
});
}
}

AndroidStudio将module变为library

apply plugin: 'com.android.application'
我们要做的就是将这一行的描述改为
apply plugin: 'com.android.library'
注意改的是要成为library的moudule,而不是调试用的module

更改后的build.gradle
接下来,我们要删除library不能有用的一些属性

android{
    defaultConfig{
        applicationId "com.my.app"
    }
}

没错,applicationId这个属性,要从library中剔除
这样,我们的app就已经是一个library了
接下来,我们来将library引用到我们的demo当中 

发布项目到Jcenter

使用gradle-bintray-plugin发布

使用bintray-release发布

1:在local.properties设置http://bintray.com账号密码

用谷歌浏览器,去掉WWW

bintray.user=aesion
bintray.apikey=67740ec8d6b6fb9ea208f74247190xxxxxxxxx

2:在项目的根目录的build:gradle文件里面添加插件代码

classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'

3:库文件项目中的build:gradle的配置:

apply plugin: 'com.android.library'

//配置插件
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'

version = "1.0.0"       //这个是版本号,必须填写
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    resourcePrefix "aesion"     //这里随便填
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.2.1'

}


def siteUrl = 'http://daidingkang.cc/'      // 项目的主页   这个是说明,可随便填
def gitUrl = 'http://daidingkang.cc/'      // Git仓库的url  这个是说明,可随便填
group = "com.daidingkang"    // 这里是groupId ,必须填写  一般填你唯一的包名

install {
    repositories.mavenInstaller {
        // This generates POM.xml with proper parameters
        pom {
            project {
                packaging 'aar'
                // Add your description here
                name 'Android View'     //项目描述
                url siteUrl
                // Set your license
                licenses {
                    license {
                        name 'The Apache Software License, Version 2.0'
                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    }
                }
                developers {
                    developer {
                        id 'aesion '        //填写开发者的一些基本信息
                        name 'daidingakng'    //填写开发者的一些基本信息
                        email 'mail@daidingkang.cc'   //填写开发者的一些基本信息
                    }
                }
                scm {
                    connection gitUrl
                    developerConnection gitUrl
                    url siteUrl
                }
            }
        }
    }
}

task sourcesJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier = 'sources'
}
task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}
artifacts {
    archives javadocJar
    archives sourcesJar
}

Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
bintray {
    user = properties.getProperty("bintray.user")    //读取 local.properties 文件里面的 bintray.user
    key = properties.getProperty("bintray.apikey")   //读取 local.properties 文件里面的 bintray.apikey
    configurations = ['archives']
    pkg {
        repo = "maven"
        name = "SnapUpCountDownTimerView"    //发布到JCenter上的项目名字,必须填写
        websiteUrl = siteUrl
        vcsUrl = gitUrl
        licenses = ["Apache-2.0"]
        publish = true
    }
}

javadoc {
    options{
        encoding "UTF-8"
        charSet 'UTF-8'
        author true
        version true
        links "http://docs.oracle.com/javase/7/docs/api"
    }
}

4:执行Terminal命令

执行命令    gradlew install
 执行命令    gradlew bintrayUpload

遍历Map的几种方式

Map类提供了一个称为entrySet()的方法,这个方法返回一个Map.Entry实例化后的对象集。接着,Map.Entry类提供了一个getKey()方法和
一个getValue()方法,Map.Entry同时也提供了一个setValue()方法,程序员可以使用它修改map里面的值。
1
2
3
4
5
6
7
8
9
HashMap<String, Stringmap = new HashMap<>();

for (int i = 0; i < 3; i++) {
map.put("name"+i, "张三"+i);
}

for (Map.Entry<String, Stringe : map.entrySet()) {
System.out.println(e.getKey()+"--"+e.getValue());
}
第二种方式和第一种一样也是通过entrySet()方法拿到Set集合,并且通过迭代器遍历出数值
1
2
3
4
5
6
7
8
9
10
11
12
13
Map   hashmap =new  HashMap();       

Set set=hashmap.entrySet();

Iterator iterator=set.iterator();

while (iterator.hasNext() {

Map.Entry mapentry = (Map.Entry) iterator.next();

System.out.println(mapentry.getkey()+"/"+ mapentry.getValue());

}

关于getsupportFragmentManager

群里最近好多人问在使用fragment的时候,获取fragmentManager爆红,提示说没有这个方法。
其实原理很简单。

首先Fragment是3.0之后的东西,3.0之后获取FragmentManager使用的方法是getFragmentManager() ,3.0之前压根没有Fragment。但是google提供了3.0之前也能用Fragment的功能,那就是使用Android-support-v4.jar兼容包,同时这时候继承就不能直接继承Activity,而要继承FragmentActivity。

办法:不需要兼容3.0,可直接继承Activity,并采用getFragmentManager()拿到FragmentManager。
需要兼容3.0,导入V4包,并且继承FragmentActivity,这时候通过getSupportFragmentManager() 拿到FragmentManager。
使用FragmentActivity是因为要向下兼容


Android LoGo尺寸设计大小

各种分辨率下LoGo的尺寸

Gson的TypeToken解析

由于在运行期间无法得知泛型的具体类型,对这个类的对象进行序列化和反序列化都不能正常进行。Gson通过借助TypeToken类来解决这个问题。

1
List<Personps = gson.fromJson(str, new TypeToken<List<Person>>(){}.getType());

获取获得 LayoutInflater 实例的三种方式

在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById()。不同点是LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化;而findViewById()是找xml布局文件下的具体widget控件(如Button、TextView等)。 具体作用:
1、对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;
2、对于一个已经载入的界面,就可以使用Activiyt.findViewById()方法来获得其中的界面元素。

1.LayoutInflater inflater = getLayoutInflater();  //调用Activity的getLayoutInflater()

2.LayoutInflater localinflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

3.LayoutInflater inflater = LayoutInflater.from(context);  

还有最后一种获取View的方法

1
View view = View.inflate(context,resource,viewGroup);

RecycleView的Item布局不能全屏的问题

今天做布局的时候,发现recycleView的item布局设置layout_width和layout_height无效的问题

之前用的是

RecyclerView.setLayoutManager(new LinearLayoutManager(context));

导致item的layout_width匹配父控件无效,应该改为

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);

才能使得Item全屏,另外其实在Adapter的时候LayoutInflater如果把父容器传递进来也许可以解决,未尝试

RecyclerView.LayoutManager介绍

RecyclerView.LayoutManager吧,这是一个抽象类,好在系统提供了3个实现类:

LinearLayoutManager 现行管理器,支持横向、纵向。
GridLayoutManager 网格布局管理器
StaggeredGridLayoutManager 瀑布就式布局管理器

举例

1
2
3
4
new LinearLayoutManager(context)
new LinearLayoutManager(Context context, int orientation, boolean reverseLayout) //reverseLayout为是否反转
new GridLayoutManager(this,4)
new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.HORIZONTAL));

Gradle报错,版本太低,解决办法

Error:(1, 1) A problem occurred evaluating project ':app'.
Failed to apply plugin [id 'com.android.application']
Gradle version 2.10 is required. Current version is 2.8. If using the gradle wrapper, try editing the distributionUrl in F:\XXX\gradle\wrapper\gradle-wrapper.properties to gradle-2.10-all.zip

Gradle的版本太低,那么怎么升级Gradle版本呢,首先打开android studio项目 找到项目目录gradle\wrapper\gradle-wrapper.properties这个文件,最后一句distributionUrl=http://services.gradle.org/distributions/gradle-2.8-all.zip就是Gradle的版本,修改成2.10即可。再次运行程序,AndroidStudio就会自动下载2.10的Gradle版本。

Activity窗口化,并设置显示的大小及位置

首先我们在Resources中添加一个主题

<style name="DialogStyle" parent="@android:style/Theme.Dialog">
    <item name="android:windowBackground">@android:color/white</item>
</style>

实现对话框风格的activity,我们需要在AndroidManifest.xml添加一句样式声明:

<activity
  android:name=".product.MyselfPayProduct"
   android:screenOrientation="portrait"
   android:theme="@android:style/DialogStyle" >

改变大小以及位置

lp.width = WindowManager.LayoutParams.FILL_PARENT;
匹配全屏

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
View view = getWindow().getDecorView();
WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.x = 10;
lp.y = 10;
lp.width = 300;
lp.height = 300;
getWindowManager().updateViewLayout(view, lp);
}

设置DiaLog的样式以及位置

值得注意的是如果setContentView(View view),会使样式布局的参数无效,因为参数ViewGroup为NUll
所以dialog也是可以用来FindViewById的

1
2
3
4
5
6
7
8
9
10
11
   WindowManager m = getWindowManager();
Display d = m.getDefaultDisplay(); // 获取屏幕宽、高用
dialog = new Dialog(this, R.style.MyDialog);
dialog.setContentView(R.layout.dialog_layout);
Window dialogWindow = dialog.getWindow();
dialogWindow.getDecorView().setPadding(0, 0, 0, 0);//解决MATCH_PARENT无效的问题
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = (int) (d.getWidth()); // 宽度设置为屏幕的0.65
dialogWindow.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
dialogWindow.setAttributes(lp);
dialog.show();

输入法带搜索按钮

Edittext设置这两个参数即可
使用android:imeOptions属性的时候,一定要对EditText设置 android:inputType 或者 设置 android:singleline=”true”

android:imeOptions="actionSearch"
android:singleLine="true"

按钮监听事件

1
2
3
4
5
6
7
8
9
10
editText.setOnEditorActionListener(this);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
Toast.makeText(MainActivity.this, v.getText().toString(), Toast.LENGTH_SHORT).show();
}
return false;
}
});

扩展知识,imeOptions属性解析

判断一个类是否在任务栈里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private boolean isExsitMianActivity(Class<?cls) {
Intent intent = new Intent(context, cls);
ComponentName cmpName = intent.resolveActivity(context.getPackageManager());
boolean flag = false;
if (cmpName != null) { // 说明系统中存在这个activity
ActivityManager am = (ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfotaskInfoList = am.getRunningTasks(20);
for (ActivityManager.RunningTaskInfo taskInfo : taskInfoList) {
if (taskInfo.baseActivity.equals(cmpName)) { // 说明它已经启动了
flag = true;
break; //跳出循环,优化效率
}
}
}
return flag;
}

拓展知识:
任务栈
判断前台任务栈

RecycleView图片如何才能适应屏幕瀑布流

1
2
3
4
5
6
7
8
9
Glide.with(activity)//更改图片加载框架
.load(activity.getString(R.string.serverbaseurl) + gallery.getImg())
.fitCenter()//重点
.crossFade()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(holder.img);


holder.img.getLayoutParams().width = DensityUtil.getWidth() / 2;//宽度为屏幕一半

布局参考

<ImageView
    android:id="@+id/iv_img"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

Android RecyclerView瀑布流位置变化和顶部留白

我们在使用Android RecyclerView实现瀑布流时,虽然很爽,但是也会存在部分问题的。

位置发生变化:

1
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);

顶部留白:

1
2
3
4
5
6
7
8
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//防止第一行到顶部有空白区域
layoutManager.invalidateSpanAssignments();
}
});

参考链接
Android 关于RecyclerView瀑布流显示图片时Item切换、闪烁等问题优化

Unsupported major.minor version 52.0

Error:(1, 0) Cause: com/android/build/gradle/AppPlugin : Unsupported major.minor version 52.0

这个错误的原因是在 工程的build.gradle

文件中的gradle 工具配置使用了gradle:+或者更高的版本

将它

classpath 'com.Android.tools.build:gradle:+'

替换为固定版本的gradle。问题解决

classpath "com.android.tools.build:gradle:2.1.0"

Android Studio解决未识别Java文件(出现红J)问题

这个问题困惑了很久,网上说的clean、build都不管用,还有在工程结构里moudle->source加入java文件,Android Studio早期版本可以正常解决,后来的新版本会发现根本找不到这个目录,经过一番折腾,终于找到如下解决方法:

切换到project视图下,找到app里的build.gradle,在android结构里插入如下代码:

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/新建的文件夹名']
    }
}

例如我的就是:

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/asmack']
    }
}

然后rebuild project,问题得到解决

Default activity not found

解决办法

如果选择 Do not launch Activity就可以成功编译。、

出现这个 Default activity not found 报错一般就是没有找到入口的Activity。之前加的.LoginActivity,可能是 AS无法识别。后来加上完整的包名com.udriving.LoginActivity就可以了。

Hexo添加站内搜索

如果Hexo打开一片空白,那是和这个主题有关和Coding没有关系,网站被分在百度云

安装 hexo-generator-search,在nanshanyi.github.io目录下执行以下命令:

$ npm install hexo-generator-search --save

在nanshanyi.github.io/themes/next/_config.yml添加

search: 
      path: search.xml
      field: post

发布即可看到效果,和上面的 swiftType 效果差不多。

注意:next 5.1.1以后只需要在主题配置文件中修改

local_search:
  enable: true

Android友盟微信分享,点击分享,可以登录,之后闪退

很奇怪,重启手机就可以用了!!!

微信的

应用签名:9c29cc8bc30f5edc087bde312ef083a1

包名:com.pulamsi.photomanager

要核实;还有APPid和AppSecret要去微信开放平台核实,且正式打包之后签名要核对

NestedScrollView属性fillViewport解决android布局不能撑满全屏的问题

无论是NestedScrollView亦或者是ScrollView都有效果
自己在做项目的时候遇到了一个NestedScrollView嵌套LinerLayout,里面是TabLayout加ViewPager。发现ViewPager不能全屏;
当ScrollView里的元素想填满ScrollView时,使用”fill_parent”是不管用的,必需为ScrollView设置:android:fillViewport=”true”。

重要代码,在ScrollView添加

1
android:fillViewport="true"

EditText的软键盘不自动弹出

1.让EditText不自动获取焦点

解决之道:在EditText的父级控件中找一个,设置成

   Android:focusable="true"  
   android:focusableInTouchMode="true"

这样,就把EditText默认的行为截断了!

2.让EditText不自动弹出软键盘

在改控件所在的Activity的AndroidManifest.xml中设置windowSoftInputMode

android:windowSoftInputMode="stateHidden|stateAlwaysHidden"

windowSoftInputMode属性拓展:

 activity主窗口与软键盘的交互模式,可以用来避免输入法面板遮挡问题,Android1.5后的一个新特性。

这个属性能影响两件事情:

【一】当有焦点产生时,软键盘是隐藏还是显示

【二】是否减少活动主窗口大小以便腾出空间放软键盘

 各值的含义:

【A】stateUnspecified:软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置

【B】stateUnchanged:当这个activity出现时,软键盘将一直保持在上一个activity里的状态,无论是隐藏还是显示

【C】stateHidden:用户选择activity时,软键盘总是被隐藏

【D】stateAlwaysHidden:当该Activity主窗口获取焦点时,软键盘也总是被隐藏的

【E】stateVisible:软键盘通常是可见的

【F】stateAlwaysVisible:用户选择activity时,软键盘总是显示的状态

【G】adjustUnspecified:默认设置,通常由系统自行决定是隐藏还是显示

【H】adjustResize:该Activity总是调整屏幕的大小以便留出软键盘的空间

【I】adjustPan:当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分

Glide缓存没有立即显示

Glide最好不要设置placeholder属性

1
2
3
4
5
6
Glide.with(MainActivity.this)//更改图片加载框架
.load(Constants.IMG_URL + ".jpg")
.centerCrop()
.crossFade()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(headerImg);
DiskCacheStrategy.NONE 什么都不缓存,就像刚讨论的那样
DiskCacheStrategy.SOURCE 仅仅只缓存原来的全分辨率的图像。在我们上面的例子中,将会只有一个 1000x1000 像素的图片
DiskCacheStrategy.RESULT 仅仅缓存最终的图像,即,降低分辨率后的(或者是转换后的)
DiskCacheStrategy.ALL 缓存所有版本的图像(默认行为)

item的水波纹效果

在你item的View加上这一条属性:

1
android:foreground="?android:attr/selectableItemBackground"

LRecycleView上拉加载不能回调的原因

不能用layoutManager,应该用staggeredGridLayoutManager

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
lRecyclerView.setLayoutManager(new LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false));

setFocusable、setEnabled、setClickable区别

setClickable  设置为true时,表明控件可以点击,如果为false,就不能点击;“点击”适用于鼠标、键盘按键、遥控器等;
注意,setOnClickListener方法会默认把控件的setClickable设置为true。

setEnabled  使能控件,如果设置为false,该控件永远不会活动,不管设置为什么属性,都无效;
设置为true,表明激活该控件,控件处于活动状态,处于活动状态,就能响应事件了,比如触摸、点击、按键事件等;
setEnabled就相当于总开关一样,只有总开关打开了,才能使用其他事件。

setFocusable 使能控件获得焦点,设置为true时,并不是说立刻获得焦点,要想立刻获得焦点,得用requestFocus;
使能获得焦点,就是说具备获得焦点的机会、能力,当有焦点在控件之间移动时,控件就有这个机会、能力得到焦点。

去掉RecycleView自带的边界回弹效果

Overscroll(边界回弹)效果– android2.3新增的功能,也就是当滑动到边界的时候,如果再滑动,就会有一个边界就会有一个发光效果。

在xml中,listView的一个属性
android:overScrollMode="never"
在代码中
mListView.setOverScrollMode(View.OVER_SCROLL_NEVER);
设置后,魅族等手机下拉时就不会显示HOLD了

Android开发之神奇的Fading Edge,让你的View更有层次感!

http://blog.csdn.net/u012702547/article/details/52913538

用SpannableString打造绚丽多彩的文本显示效果

用SpannableString打造绚丽多彩的文本显示效果

谈谈Android中的Rect类——奇葩的思维

最近在工作中遇到了一些问题,总结下来就是Android中Rect这个类造成的。不得不说,不知道Android SDK的开发人员是怎么想的, 这个类设计的太奇葩了。首先介绍一下Rect类:Rect类主要用于表示坐标系中的一块矩形区域,并可以对其做一些简单操作。这块矩形区域,需要用左上右下两个坐标点表示(left,top,right,bottom),你也可以获取一个Rect实例的Width和Height。就在这里,奇葩的事情来了,作为一个有一点经验的做图像或者矩阵运算或者编程的程序员来说,大家的共识是,如果一个矩阵是MxN的,也就是M行N列,那么行号是[0,M-1],列号是[0,N-1]。可是奇葩的Rect类并不是这样的!如果你这么声明一个Rect类:

Rect rect=new Rect(100,50,300,500);

那么右下角(300,500)其实是不在这个矩形里面的,但是左上角(100,50)在,也就是说,这个矩形实际表示的区域是:(100,50,299,499)。另外,Rect计算出的Height和Width倒是对的。所以,在此告诫各位程序员,在涉及Rect运算的时候,尽量不要使用它的右下角左边,即right和bottom。因为他们是错的。当然,在你调用android自己的函数时,是可以使用的,因为Android里面一直保持这么奇葩的思维。

new Rect(150, 75, 260, 120)  

这个构造方法需要四个参数这四个参数 指明了什么位置 ?我们就来解释怎么画 这个 矩形
这四个 参数 分别代表的意思是:left top right bottom 上下左右呗。啊,不是 是 左 上 右 下。 下面给大家解释

left : 矩形左边的X坐标  150        
top:    矩形顶部的Y坐标   75        
right :  矩形右边的X坐标   260    
bottom: 矩形底部的Y坐标 120     

说白了就是左上角的坐标是(150,75),右下角的坐标是(260,120),这样就好理解了

自定义控件如果没有Activity的重载方法

可以在目标Activity中重载完回调

例如: onActivityResult

以此类推

由相机或图库中的图片bitmap与uri互相转换

1、bitmap to uri

Uri uri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, null,null));

2、uri to bitmap

Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);

Activity传递Bitmap的方式

一,通过Intent的Bundle。
比如有两个activity,A,B,从A进入B。先在A中将Bitmap写进去:

1
2
3
4
5
6
7
8
9
Resources res=getResources();  
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.ic_launcher);

Bundle b = new Bundle();
b.putParcelable("bitmap", bmp);

Intent intent = new Intent(this, MainActivity2.class);
intent.putExtras(b);
startActivity(intent);

然后在B中解析、接收Bitmap:

1
2
3
Intent intent=getIntent();  
Bundle b=intent.getExtras();
Bitmap bmp=(Bitmap) b.getParcelable("bitmap");

此种传递方式的缺陷:只能传递相对较小适中大小的Bitmap,如果Bitmap大小尺寸过大就会引起代码崩溃。

二,把Bitmap写进字节流。
比如有两个activity,A,B,从A进入B。先在A中将Bitmap写进字节流传递出去:

1
2
3
4
5
6
7
8
9
10
11
12
Resources res=getResources();  
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.ic_launcher);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] bytes=baos.toByteArray();

Bundle b = new Bundle();
b.putByteArray("bitmap", bytes);

Intent intent = new Intent(this, MainActivity2.class);
intent.putExtras(b);
startActivity(intent);

然后在B中接收Bitmap的字节流并恢复出来:

1
2
3
4
5
Intent intent=getIntent();  
Bundle b=intent.getExtras();
byte[] bytes=b.getByteArray("bitmap");

Bitmap bmp=BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

此种传递方式的缺陷:缺陷同第一种方式相同。

小结:

以上两种方式均适用于适中、较小图片,如果图片过大如MB量级的,就不能正常工作了。

最好的办法是传Uri,然后转换成Bitmap

Android工程文件下assets文件夹与res文件夹的区别

1.assets:

不会在R.java文件下生成相应的标记,assets文件夹可以自己创建文件夹,必须使用AssetsManager类进行访问
存放到这里的资源在运行打包的时候都会打入程序安装包中,

2.res:

会在R.java文件下生成标记,这里的资源会在运行打包操作的时候判断哪些被使用到了,没有被使用到的文件资源是不会打包到安装包中的。

res/raw和assets文件夹来存放不需要系统编译成二进制的文件,例如字体文件等

在res文件夹下还可以定义一下目录:

res/anim:这里存放的是动画资源。

res/xml:可以在Activity中使用getResource().getXML()读取这里的资源文件

res/raw:该目录下的文件可以直接复制到设备上,不能有子文件夹,编译软件时,这里的数据不需要编译,直接加入到程序安装包中,使用方法是getResource().OpenRawResources(ID),其中参数ID的形式是R.raw.XXX.

Android Studio下添加assets目录

在Android Studio中添加assets目录,目录的位置在
在CODE上查看代码片派生到我的代码片

XXX\src\main\assets  

XXX代表你的项目的路径,assets放在src\main目录下。

这个位置可以通过XXX.iml XXX代表自己的项目名,其中有设置assets的目录,设置如下

在CODE上查看代码片派生到我的代码片

<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" / 

关于在Android Studio中使用Assets目录下的资源的问题

Android Studio 添加Assets目录

Activity之间传递数组(File[] 数组)

传统的intent不能传递对象数组

1.新建一个Bean,继承Serializable接口,把File[]作为参数传递进去,如下

1
2
3
4
5
6
7
8
9
10
11
public class SerializableData implements Serializable {
private File[] files;

public File[] getFiles() {
return files;
}

public void setFiles(File[] files) {
this.files = files;
}
}

2.发送数据

1
2
3
4
5
6
7
Intent intent = new Intent(NineCutToolActivity.this,NineCutToolResultActivity.class);
Bundle bundle = new Bundle() ;
SerializableData serializableData = new SerializableData();
serializableData.setFiles(nineCilpFile);
bundle.putSerializable("SerializableData", serializableData);
intent.putExtras(bundle);
startActivity(intent) ;

3.接收数据

1
serializableData = (SerializableData) getIntent().getExtras().getSerializable("SerializableData");

BitMap的压缩

bitmap的六种压缩方式,Android图片压缩

微信实现本地视频发布到朋友圈功能

这个确实厉害了,反编译看源码

Android”挂逼”修炼之行—微信实现本地视频发布到朋友圈功能

EditText 禁止输入emoji(过滤emoji)

EditText 禁止输入emoji的方式就是用InputFilter过滤调emoji表情。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 给edittext设置过滤器 过滤emoji
*
* @param et
*/
public static void setEmojiFilter(EditText et) {
InputFilter emojiFilter = new InputFilter() {
Pattern pattern = Pattern.compile("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);

@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Matcher matcher = pattern.matcher(source);
if(matcher.find()){
return "";
}
return null;
}
};
et.setFilters(new InputFilter[]{emojiFilter});
}

Android 从系统媒体库中选择视频

Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI);

打开方式有两种action,1.ACTION_PICK;2.ACTION_GET_CONTENT 区分大意为:ACTION_PICK 为打开特定数据一个列表来供用户挑选,其中数据为现有的数据。而 ACTION_GET_CONTENT 区别在于它允许用户创建一个之前并不存在的数据。

Android 打开相册选择 照片、音频、视频

ViewStub的使用

标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。各种不常用的布局想进度条、显示错误消息等可以使用标签,以减少内存使用量,加快渲染速度。是一个不可见的,大小为0的View。标签使用如下:

1
2
3
4
5
6
7
<ViewStub  
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" /

当你想加载布局时,可以使用下面其中一种方法:

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);  
// or  
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate(); 

布局重用

标签能够重用布局文件,简单的使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:orientation="vertical"
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background="@color/app_bg"
android:gravity="center_horizontal"

<include layout="@layout/titlebar"/

<TextView android:layout_width=”match_parent”
android:layout_height="wrap_content"
android:text="@string/hello"
android:padding="10dp" /

...

触摸事件的分发

参考:https://www.jianshu.com/p/e99b5e8bd67b

简单来说触摸事件的分发会经过这么几个顺序,dispatchTouchEvent –onInterceptTouchEvent –onTouchEvent,事件拦截就在onInterceptTouchEvent方法中进行,在该方法中返回true即代表拦截触摸事件。触摸事件的分发是一个典型的隧道事件,即从上到下的过程。从视图树角度上来说,就是触摸事件会从父视图挨个传递到子视图。比如一个LinearLayout中又一个TextView,当触摸这个TextView时触摸事件会先打到LinearLayout,然后再到达TextView。如果LinearLayout将触摸事件拦截了,那么TextView就会收到一个CANCEL事件,其他触摸就收不到了。但是触摸事件的处理过程是一个冒泡事件,还是以上面的TextView为例,正常情况下,事件从上到下分发到TextView上,TextView则会对该事件进行处理,如果TextView处理了该事件,即TextView的dispatchTouchEvent返回了true, 那么该事件就被消费了。但是如果TextView的dispatchTouchEvent返回的是false, 则代表这个事件没有被处理,此时该事件就会从下到上(即从child 到 view group的过程)找parent view进行处理。如果parent view也没有处理,那么最终会交给Activity (如果是Activity窗口) 的onTouchEvent来处理。下面就是ViewGroup的事件分发过程,更详细的资料请参考Android Touch事件分发过程。

dispatchTouchEvent 是向下传递(下潜),onTouchEvent是向上传递(冒泡);

总之:

  1. dispatchTouchEvent返回false的话,自己不处理(自己的onTouchEvent不执行),交给上层来处理(上层的onTouchEvent执行)。
  2. onInterceptTouchEvent返回true的话,自己处理(自己的onTouchEvent执行)。
  3. requestDisallowInterceptTouchEvent(true) 这个方法能够影响父View是否拦截事件,true 表示父 View 不拦截事件,false 表示父 View 拦截事件。

ViewGroup有一个disallowIntercept开关,可以设置此ViewGroup是否屏蔽onInterceptTouchEvent事件。如果开启此开关(为True),则此ViewGroup跳过自身的onInterceptTouchEvent事件(也就是不执行了),直接dispatchTouchEvent到子View。

如图:

True为消费,False为不消费

为True的完整流程:

###滑动冲突的拦截

详细:http://blog.csdn.net/gdutxiaoxu/article/details/52939127

外部拦截法

onInterceptTouchEvent()方法拦截

内部拦截法

requestDisallowInterceptTouchEvent(boolean)方法拦截

###总结

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,

  • 如果dispatchTouchEvent返回true 消费事件,事件终结。
  • 如果dispatchTouchEvent返回 false ,则回传给父View的onTouchEvent事件处理;

onTouchEvent事件返回true,事件终结,返回false,交给父View的OnTouchEvent方法处理

  • 如果dispatchTouchEvent返回super的话,默认会调用自己的onInterceptTouchEvent方法

默认的情况下interceptTouchEvent回调用super方法,super方法默认返回false,所以会交给子View的onDispatchTouchEvent方法处理

如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,

如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。

水波纹按钮

android:background="?android:attr/selectableItemBackground"

将urlencoded字符串转换成普通字符串。

java.net.URLDecoder.decode(String s,"UTF-8");

将普通字符串转换成urlencoded字符串

java.net.URLEncoder.decode(String s,"UTF-8");

android:parentActivityName的作用

例如:对方像你发送一条消息,你应用程序处于退出状态,这时你收到一条消息推送,点击之后进入消息会话界面。此时你点击返回,并不是回到MainActivity,而是退出了会话界面,回到桌面;
需求:点击返回的时候回到APP主页面

Android 4.1 (Jelly Bean) 增强了通知功能,在AndroidManifest.xml中可以为Activity添加属性android:parentActivityName="MainActivity",实现点击通知打开ResultActivity时,再按返回键,
回到MainActivity,而不是回到之前的Task。只在4.1的机器上有效,在2.x的机器上和以前没有任何区别。

但是大部分布局的TitleBar都是自定义的,所以以上方法并不适用

也可以在返回按钮设置:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (KeyEvent.KEYCODE_BACK == event.getKeyCode()) {
        startActivity(new Intent(this, MainActivity.class));
        finish();
    }
    return false;
}

拓展:
在设置了android:parentActivityName后,点击子Activity返回键,父Activity总会调用OnDestroy()的解决方案

腾讯应用宝认领,空白APK签名

格式为:

jarsigner -verbose -keystore [keystorePath] -signedjar [apkOut] [apkIn] [alias]  

举例:

jarsigner -verbose -keystore C:\debug.keystore -signedjar C:\ssss.apk C:\tap_unsign.apk android

参数说明:

jarsigner命令格式:-verbose输出详细信息 -keystore密钥库位置 -signedjar要生成的文件 要签名的文件 密钥库文件
keystorePath参数代表keyStore的绝对路径,如D:\keystore
apkOut参数代表签名后的apk路径,如D:\signed.apk
apkin参数代表在腾讯应用中心下载的未签名apk,默认名称为tap_unsign.apk
alias参数代表签名用的整数名称(创建keyStore时所填写),如android,tianma...(下拉选择那个)

使用方式:

jarsigner这个exe在C:\Program Files\Java\jdk1.7.0_10\bin文件夹下。所以要用cmd进入这个文件夹然后使命令

Android Studio SVN 使用方法

Android Studio SVN 使用方法

Android studio如何使用SVN进行版本控制

View内容生成图片

我们可以将界面上的View生成为BitMap

生成图片的操作比较耗时,虽然是View对象,但咱是直接把View对象转成图片,不涉及修改UI显示,可以异步处理。

1
2
3
4
5
6
7
contentLayout.setDrawingCacheEnabled(true);     
contentLayout.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
contentLayout.layout(0, 0, contentLayout.getMeasuredWidth(),
contentLayout.getMeasuredHeight());
contentLayout.buildDrawingCache();
Bitmap bitmap= contentLayout.getDrawingCache();

经测试,下面这段代码会使页面发生重绘的情况,如果不想重绘,请注释掉

1
2
contentLayout.layout(0, 0, contentLayout.getMeasuredWidth(),     
contentLayout.getMeasuredHeight());

Canvas开篇之drawBitmap

canvas.drawBitmap(mBitmap, mSrcRect, mDestRect, mBitPaint);  

第一个 mSrcRect 代表要绘制的bitmap区域(也就是我们需要绘制上去的Bitmap的哪一块部分),第二个 mDestRect 代表的是要将bitmap 绘制在屏幕的什么地方

Activity设置singleTask无法通过Intent获取值的问题

在需要启动另一个activity,并传递一些数据时,我们常采取如下的方法:

1
2
3
4
5
Intent intent = new Intent(this,  ActivityB.class); 

intent.putExtra("name", mUserName);

startActivity(intent);

同时在ActivityB中的onCreate()或onResume()方法中获取传递的数据:

1
2
3
4
5
6
7
Bundle bundle = getIntent().getExtras(); 

if (bundle != null && bundle.containsKey("name")) {

mUserName = bundle.getString("name");

}

但是,当把ActivityB的加载方式设置为singleTask或singleInstance时,我们会发现,除了第一次能正确接收以外,其他的好像都是为空?

原来,activity的getIntent()方法只是获取activity原来的intent。因此要想解决上述问题,可采用的办法之一是重载onNewIntent()方法。

1
2
3
4
5
6
7
8
9
10
@Override 
protected void onNewIntent(Intent intent) {

super.onNewIntent(intent);

setIntent(intent);

//here we can use getIntent() to get the extra data.

}

Toolbar 设置OverFlow 图片

点点点的图标

一种方法设置Toolbar 的Theme

参照:http://www.jianshu.com/p/79604c3ddcae

另一种就是直接在代码中设置

toolbar.setOverflowIconContextCompat.getDrawable(this,R.mipmap.ic_menu_more_overflow));

Fiddle模拟Post请求

如果你是以JSON的形式发送的话

{"参数名":"参数值","参数名":"参数值",...} 

{"number": "adminicxp","member_id":"8defc65c-808c-4389-9e47-2854916869e9"} 

头文件这么写

User-Agent: Fiddler
//这一句是重点
Content-Type: application/json; charset=utf-8
Content-Length: 138

参数拼接如下:

如果你是以表单的形式发送的话(例如我们安卓的Post请求)

name1=valule1&name2=value2...  

头文件这么写

User-Agent: Fiddler
Content-Length: 138
//这一句是重点
Content-Type: application/x-www-form-urlencoded

记录下Hexo Next 主题的更新

由于多说评论的关闭,我不得不使用新的评论系统。说来多说也是很可惜的,因为我有评论的数据在那上面,关闭意味着我会丢失所有以前的评论数据
。现在改用网易云跟帖,总得来说还好,但是我希望稳定一点最好。

那么更改评论框架,就需要涉及到更新主题,现在的方法是去官网把NEXT主题的包下载下来,然后覆盖你本地的NEXT文件,然后再改一些配置文件就OK了。

Tablayout添加分割线

TabLayout的标签之间是默认没有分割线的,如果我们想添加分割线,让标签之间更有层次感的话,可以添加以下的代码:

1
2
3
4
5
6
//设置分割线
LinearLayout linearLayout = (LinearLayout) tabLayout.getChildAt(0);
linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,
R.drawable.divider)); //设置分割线的样式
linearLayout.setDividerPadding(dip2px(10)); //设置分割线间隔

自定义的分割线样式:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#80c0c0c0" />
<size android:width="1dp" />
</shape>

setDividerPadding方法中输入的参数是单位是dp,我们需要转换成像素:

1
2
3
4
5
//像素单位转换
public int dip2px(int dip) {
float density = getResources().getDisplayMetrics().density;
return (int) (dip * density + 0.5);
}

去掉RecycleView或者ListView的下拉回弹效果

不同的安卓厂商对ListView或ScrollView都做了一些动画效果,比如下拉时为了产生弹性美感而有大幅度回弹效果

1
android:overScrollMode="never"

MarkdownPad2.5 注册码

邮箱:

Soar360@live.com

授权秘钥:

GBPduHjWfJU1mZqcPM3BikjYKF6xKhlKIys3i1MU2eJHqWGImDHzWdD6xhMNLGVpbP2M5SN6bnxn2kSE8qHqNY5QaaRxmO3YSMHxlv2EYpjdwLcPwfeTG7kUdnhKE0vVy4RidP6Y2wZ0q74f47fzsZo45JE2hfQBFi2O9Jldjp1mW8HUpTtLA2a5/sQytXJUQl/QKO0jUQY4pa5CCx20sV1ClOTZtAGngSOJtIOFXK599sBr5aIEFyH0K7H4BoNMiiDMnxt1rD8Vb/ikJdhGMMQr0R4B+L3nWU97eaVPTRKfWGDE8/eAgKzpGwrQQoDh+nzX1xoVQ8NAuH+s4UcSeQ==

MarkdownPad2 支持语法高亮

MarkdownPad免费版默认设置是不支持程序设计语言的语法高亮的,需要先升级到付费版(MarkdownPad Pro)。

按照菜单栏Tools → Options → Markdown → MarkdownPad Processor → GitHub Flavored Markdown (或者在编辑器左下角)设置才能启用,效果如下:

改成Github的风格,最好登录下账号,不过滑动有点卡顿,不卡顿可以用离线版本

关于app退出的问题,完美退出方式和栈管理

实际开发中会有很多关于app的退出问题,我个人比较常见的有两种:

一、双击退出

比如说我们在首页的时候需要一个双击退出的方法,点击第一次手机的返回键时提示:“再点一次退出应用”。之类的话语,我们可以这样做,对重写onKeyDown方法,当他第一次点击的时候提示文字,给一个几秒的间隔,在这个时间段内第二次点击退出,才退出应用,首先定义一个静态变量:

1
private static boolean isExit = false;

然后重写onKeyDown方法,点击在某个时间段内点击两次退出,才退出应用,当然我们不能再主线程里面操作视图,这样我们就需要利用到handler,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//主线程处理视图,isExit默认为false,就是点击第一次时,弹出"再按一次退出程序"  
//点击第二次时关闭应用
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
isExit = false;
}
};

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
exit();
return false;
}
return super.onKeyDown(keyCode, event);
}

/**
* 点击两次退出程序
*/
private void exit() {
if (!isExit) {
isExit = true;
Toast.makeText(getApplicationContext(), "再按一次退出程序",
Toast.LENGTH_SHORT).show();
// 利用handler延迟发送更改状态信息
mHandler.sendEmptyMessageDelayed(0, 2000);
} else {
finish();
//参数用作状态码;根据惯例,非 0 的状态码表示异常终止。
System.exit(0);
}
}

二、在登录页面点击退出

在实际开发中,我们需要在个人中心退出登录,当我们退出登录后我们一般正常逻辑是退出当前页面跳转到登录页面,这是我们就在登录页面可以重新登录,或者退出应用,这是我们点击返回键就不是返回上一个页面了,如果这时我们返回上个页面就很尴尬了,就比如说我退出登录了然后我返回我又是登录页面。当然你可以在再页面中在onResume中写个一个刷新页面的方法,但是我个人觉得这时你退出后在返回总是不合理的即使你做了刷新操作刷新掉了登录状态,但是我觉得总不好

因此我们需要把登录页面的退出改为点击手机返回键后提示弹框是否退出应用,点击确定在activity栈中找到所有的activity销毁掉然后退出,这样我们可以首先写一个appManager的Activity的管理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
  
/**
* Created by </span>唐小糖 on 2017/6/15.<span style="font-size:14px;">
* 应用程序Activity管理类:用于Activity管理和应用程序退出
* 可以让所有的activity都继承BaseActivity然后给activity在onCreate中添加到栈中onDetroyed中移除
*
* 添加Activity到堆栈
* AppManager.getAppManager().addActivity(this);
* 结束Activity&从堆栈中移除
* AppManager.getAppManager().finishActivity(this);
*
*/

public class AppManager {
private static Stack<Activity> activityStack;
private static AppManager instance;

private AppManager(){}
/**
* 单一实例
*/
public static AppManager getAppManager(){
if(instance==null){
instance=new AppManager();
}
return instance;
}
/**
* 添加Activity到堆栈 */
public void addActivity(Activity activity){
if(activityStack==null){
activityStack=new Stack<Activity>();
}
activityStack.add(activity);
}
/**
* 获取当前Activity(堆栈中最后一个压入的)
*/
public Activity currentActivity(){
Activity activity=activityStack.lastElement();
return activity;
}
/**
* 结束当前Activity(堆栈中最后一个压入的)
*/
public void finishActivity(){
Activity activity=activityStack.lastElement();
finishActivity(activity);
}
/**
* 结束指定的Activity
*/
public void finishActivity(Activity activity){
if(activity!=null){
activityStack.remove(activity);
activity.finish();
activity=null;
}
}
/**
* 结束指定类名的Activity
*/
public void finishActivity(Class<?> cls){
for (Activity activity : activityStack) {
if(activity.getClass().equals(cls) ){
finishActivity(activity);
}
}
}
/**
* 结束所有Activity
*/
public void finishAllActivity(){
for (int i = 0, size = activityStack.size(); i < size; i++){
if (null != activityStack.get(i)){
activityStack.get(i).finish();
}
}
activityStack.clear();
}
/**
* 退出应用程序
*/
public void AppExit(Context context) {
try {
finishAllActivity();
ActivityManager activityMgr= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
activityMgr.restartPackage(context.getPackageName());
System.exit(0);
} catch (Exception e) { }
}
}

在写好管理类后我们有两种方法去做,第一、我们可以让所有的Activity继承BaseActivity,然后在BaseActivity中的oncreate方法中把这个Activity加入到栈里去

1
2
//添加Activity到堆栈  
AppManager.getAppManager().addActivity(this);

在onDestroyed中销毁掉

1
2
//结束Activity&从堆栈中移除  
AppManager.getAppManager().finishActivity(this);

加起来就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 添加Activity到堆栈
AtyContainer.getInstance().addActivity(this);
}

@Override
protected void onDestroy() {
super.onDestroy();
// 结束Activity&从栈中移除该Activity
AtyContainer.getInstance().removeActivity(this);
}

}

最后在登录页面点击退出出现弹框,确定后我们调用AppManager的退出方法,登录页面代码如下

1
2
3
4
5
6
7
8
@Override  
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
CommonUtils.Exit(this);
return true;
}
return super.onKeyDown(keyCode, event);
}

CommonUtils类代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/** 
* 登录页面点击返回键退出应用
*/

public class CommonUtils {

/**
* 退出程序
* @param cont
*/

public static void Exit(final Context cont)
{
AlertDialog.Builder builder = new AlertDialog.Builder(cont);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle("确定退出吗?");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
//退出
AppManager.getAppManager().AppExit(cont);
}
});
builder.setNegativeButton("再逛逛", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
}

第二种、方法就是如果你不继承BaseActivity的话就在每个Activity中在oncreate中添加到appManager中的栈,onDestroy中移除,总之一句就是有多少个Activity就执行多少次

拓展:
APP应用完美退出的方法的尝试

这种方法我试了第二种,并不能完全退出。第一种尚未测试

Android退出应用最优雅的方式(改进版)

这里面例举了两种方式,第一种和上文一模一样。并且这里总共写了有六种方法,任君挑选

Android堆栈管理的类ActivityStackManager

管理栈的

值得注意的是:
App的MainActivity设为SingleTask的话,按Home键返回Launcher后,再次点击Launcher上的App入口,会导致MainActivity上的所有其它Activity全部出栈,这点需要注意。

Android Studio 打包时 Signature Version V1 V2

前几天更新了一下Android Studio ,今天在打正式包的时候发现多了个签名版本选择

从图中可以看到多了签名版本的选择,因为刚开始默认勾选的v2(Full APK Signature),没多想一路下一步下去,
结果在测试机上(5.0.1)一直都安装失败,想着和那个选择签名版本有关系,那就查查吧。

问题描述(v1和v2)

android 7.0中引入了APK Signature Scheme v2,v1呢是jar Signature来自JDK
V1:应该是通过ZIP条目进行验证,这样APK 签署后可进行许多修改 - 可以移动甚至重新压缩文件。
V2:验证压缩文件的所有字节,而不是单个 ZIP 条目,因此,在签名后无法再更改(包括 zipalign)。正因如此,现在在编译过程中,我们将压缩、调整和签署合并成一步完成。好处显而易见,更安全而且新的签名可缩短在设备上进行验证的时间(不需要费时地解压缩然后验证),从而加快应用安装速度。

解决方案一

v1和v2的签名使用
只勾选v1签名并不会影响什么,但是在7.0上不会使用更安全的验证方式
只勾选V2签名7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证
同时勾选V1和V2则所有机型都没问题

解决方案二

在app的build.gradle的android标签下加入如下:

1
2
3
4
5
6
7
8
9
10
signingConfigs {  
debug {
v1SigningEnabled true
v2SigningEnabled true
}
release {
v1SigningEnabled true
v2SigningEnabled true
}
}

Java String 占位符的使用

怕忘记,啊哈哈哈

1
2
3
4
5
6
7
String stringFormat  = "lexical error at position %s, encountered %s, expected %s ";    

String messageFormat ="lexical error at position {0}, encountered {1}, expected {2}";

System.out.println(String.format(stringFormat, 123, 100, 456));

System.out.println(MessageFormat.format(messageFormat, new Date(), 100, 456));

2种方式 主要是占位符不一样,好看下结果是

1
2
3
lexical error at position 123, encountered 100, expected 456     

lexical error at position 10-10-12 下午9:35, encountered 100, expected 456

看了下MessageFormat的api说明,这个占位符参数功能更加强大点,支持type,style等限定。所以如果需要使用高级功能建议是使用MessageFormat。

用迅雷下载Genymotion的virtual device的办法

1.先正常操作一次Add设备,然后取消

2.打开C:\Users\用户名\AppData\Local\Genymobile目录

3.打开genymotion.log文件,在里面最下面几行,找到类似如下日志

高亮的就是下载地址,直接把地址复制到迅雷去下载,下好后把ova文件放到

C:\Users\用户名\AppData\Local\Genymobile\Genymotion\ova目录

重启Genymotion再添加一次就行了

FragmentActivity和Activity的区别

Fragment是Android 3.0以后的东西,为了在低版本中使用Fragment就要用到android-support-v4.jar兼容包,而FragmentActivity就是这个兼容包里面的,它提供了操作Fragment的一些方法,其功能跟3.0及以后的版本的Activity的功能一样。
下面是API中的原话:

FragmentActivity is a special activity provided in the Support Library to handle fragments on system versions older than API level 11. If the lowest system version you support is API level 11 or higher, then you can use a regular Activity.

主要区别如下:

  • FragmentActivity 继承自Activity,用来解决Android 3.0之前无法使用Fragment的问题,所以在使用的时候需要导入android-support-v4.jar兼容包,同时继承 FragmentActivity,这样在Activity中就能嵌入Fragment来实现你想要的布局效果。

  • 当然Android 3.0之后你就可以直接继承自Activity,并且在其中嵌入使用Fragment。

  • 获得FragmentManager的方式也不同
    Android 3.0以下:getSupportFragmentManager()
    Android 3.0以上:getFragmentManager()

Android Studio插件整理

Android Studio插件整理 | 习惯沉默的Blog

最酷的 30 个 Android 库 - 2017

2017 春季最酷的 30 个 Android 库 - 技术翻译 - 开源中国社区

Android中windowTranslucentStatus与windowTranslucentNavigation的一些设置(沉浸式)

Android中windowTranslucentStatus与windowTranslucentNavigation的一些设置 - zjt593688的博客 - CSDN博客

Android版本和API Level的对应关系表

Code name Version API level
M 6.0 API level 23
Lollipop 5.1 API level 22
Lollipop 5.0 API level 21
KitKat 4.4 - 4.4.4 API level 19
Jelly Bean 4.3.x API level 18
Jelly Bean 4.2.x API level 17
Jelly Bean 4.1.x API level 16
Ice Cream Sandwich 4.0.3 - 4.0.4 API level 15, NDK 8
Ice Cream Sandwich 4.0.1 - 4.0.2 API level 14, NDK 7
Honeycomb 3.2.x API level 13
Honeycomb 3.1 API level 12, NDK 6
Honeycomb 3.0 API level 11
Gingerbread 2.3.3 - 2.3.7 API level 10
Gingerbread 2.3 - 2.3.2 API level 9, NDK 5
Froyo 2.2.x API level 8, NDK 4
Eclair 2.1 API level 7, NDK 3
Eclair 2.0.1 API level 6
Eclair 2.0 API level 5
Donut 1.6 API level 4, NDK 2
Cupcake 1.5 API level 3, NDK 1
(no code name) 1.1 API level 2
(no code name) 1.0 API level 1

Android性能优化之常见的内存泄露

Android性能优化之常见的内存泄露 | zhengxiaoyong

戴定康 wechat
欢迎您扫一扫上面的微信,加我为好友!