Android自定义控件包含两个TextView

#前言

为了解决美芽个人页面布局类似显示 “粉丝 12”,颜色不一,间距确定而开发的;

设定属性

在attr.XML中设定如下属性

1
2
3
4
5
6
7
8
<declare-styleable name="MultiTextViewItem">
<attr name="mtv_textColor_left" format="color"/>
<attr name="mtv_textColor_right" format="color"/>
<attr name="mtv_textSize" format="dimension|reference"/>
<attr name="mtv_tv_margin" format="dimension"/>
<attr name="mtv_text_left" format="string|reference"/>
<attr name="mtv_text_right" format="string|reference"/>
</declare-styleable>

#新建类继承于LinearLayout;

##

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
/**
* Created by shouwang on 16/3/23.
* 个人页面
* 用来显示关注 12 粉丝 4
* 便于调整间距 来与设计稿相符
*/
public class MultiTextViewItem extends LinearLayout {
TextView tvLeft;
TextView tvRight;
String textLeft;
String textRight;


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

public MultiTextViewItem(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.view_multitle_textview, this, true);
findViews();
setupByAttrs(context,attrs);

}

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

public MultiTextViewItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

void findViews() {
tvLeft = (TextView) findViewById(R.id.tvLeft);
tvRight = (TextView) findViewById(R.id.tvRight);
}

void setupByAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MultiTextViewItem, 0, 0);
String text_left = typedArray.getString(R.styleable.MultiTextViewItem_mtv_text_left);
String text_right = typedArray.getString(R.styleable.MultiTextViewItem_mtv_text_right);
int color_left = typedArray.getInt(R.styleable.MultiTextViewItem_mtv_textColor_left, -1);
int color_right = typedArray.getInt(R.styleable.MultiTextViewItem_mtv_textColor_right, -1);
int tv_margin = typedArray.getDimensionPixelSize(R.styleable.MultiTextViewItem_mtv_tv_margin, -1);
float text_size=typedArray.getDimension(R.styleable.MultiTextViewItem_mtv_textSize,12);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tvRight.getLayoutParams();
lp.setMargins(tv_margin, 0, 0, 0);
tvRight.setLayoutParams(lp);

tvLeft.setText(text_left);
tvRight.setText(text_right);
tvLeft.setTextColor(color_left);
tvRight.setTextColor(color_right);
//==字体单位
tvLeft.setTextSize(TypedValue.COMPLEX_UNIT_PX,text_size);
tvRight.setTextSize(TypedValue.COMPLEX_UNIT_PX,text_size);
setOrientation(HORIZONTAL);
typedArray.recycle();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

public void setTextLeft(String text){
textLeft=text;
tvLeft.setText(text);
}
public void setTvRight(String text){
textRight=text;
tvRight.setText(text);
}

#新建布局

##

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
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tvLeft"
android:gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textColor="@color/text_light_black_light"
android:textSize="11sp"

android:text="关注"
/>
<TextView
android:id="@+id/tvRight"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="32259"
android:textSize="11sp"
android:textColor="@color/text_black_lighter"/>
</merge>

#运用控件

xml中引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<cn.app.meiya.aa.widget.MultiTextViewItem
android:id="@+id/tvFollowCount"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:includeFontPadding="false"
android:paddingBottom="12dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="12dp"
app:mtv_textSize="13sp"
app:mtv_text_left="关注"
app:mtv_text_right="322"
app:mtv_textColor_right="@color/text_black_lighter"
app:mtv_textColor_left="@color/text_light_black_light"
app:mtv_tv_margin="7dp">

</cn.app.meiya.aa.widget.MultiTextViewItem>

#关于抽象布 http://blog.csdn.net/xyz_lmn/article/details/14524567

关于自定义属性

参考 http://blog.csdn.net/android_tutor/article/details/5508615

Android Fragment笔记

关于Fragment

Fragment 释义为片段;界面的一部分

与Activity之间的关联

依附于宿主Activity,同样拥有生命周期

onAttach、onCreate、onCreateView、onStart、onResume、onPause、onStop、onDestroyView、onDestroy

#构造方式

##xml配置

1
2
3
4
5
6
7
8
9
10
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:tag="tag"
android:name="com.asha.fragmentdemo.MyDialogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

Android 签名管理

采用jdk生成签名

##

1
keytool -genkey -alias android.keystore -keyalg RSA -validity 20000 -keystore android.keystore

查看签名信息

##

1
keytool -list -v -keystore debug.keystore

给项目签名

可以在项目文件下新建keystoere.properties,内容写上

1
2
3
4
keystore=sw.keystore
storePassword=123456
keyAlias=sw.keystore
keyPassword=123456

把事先生成的签名文件复制在根目录下。

#Gradle 脚本

##

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java.io.File signFile=rootProject.file("keystoere.properties")
if(signFile.exists()){
System.println("file exist");
Properties properties=new Properties();
properties.load(new FileInputStream(signFile));
signingConfigs{
release{
storeFile rootProject.file(properties['keystore'])
storePassword properties['storePassword']
keyAlias properties['keyAlias']
keyPassword properties['keyPassword']
System.println(storeFile);
System.println(keyAlias);
System.println(keyPassword);
}
}
}else{
System.println("file not exist");
}

注意:写脚本时候,变量要定义在被引用之前;
如果出现读取签名文件错误 检查信息是否写错;

#运行脚本打包

##

  1. ./gradlew assemRelease 正式包
  2. ./gradlew assemDebug 打测试包

参考文章:https://yeungeek.gitbooks.io/gradle-plugin-user-guide/content/signing_configurations.html

Android Activity的详解

Activity的生命周期

在Android中Activity状态分为:1.激活态(Active)一个新的Activity入栈后,展示屏幕的最前端,可以与用户交互;2.OnPause:当一个Activity被一个透明或者Dialog样式的Activity给覆盖时的状态,依然可见,但是失去焦点,不能与用户交互;

3.onStop:失去焦点(同样是被覆盖、不可见)
4.Killed:被系统杀死回收或者没有被启动。

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
@Override
protected void onStart() {
super.onStart();
// onCreate() 方法之后被调用,或者在 Activity 从 Stop 状态转换为 Active 状态时被调用.
}
@Override
protected void onStop() {
Log.i(Tag,"OnStop=====================");//Activity变成不可见 失去焦点
super.onStop();
//Activity 从 Active 状态转换到 Stop 态时被调用。一般我们在这里保存 Activity 的状态信息
}
@Override
protected void onPause() {
Log.i(Tag,"onPause=====================");//Activity状态依然可见 失去jiaodian不能交互//1
super.onPause();
}
@Override
protected void onResume() {
Log.i(Tag,"onResume=====================");
super.onResume();
//Activity 从 Pause 状态转<--> Active 状态时被调用。
}
@Override
protected void onDestroy() {
super.onDestroy();
}

Activity栈

Android 是通过一种 Activity 栈的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。处于前台的 Activity 总是在栈的顶端,当前台的 Activity 因为异常或其它原因被销毁时,处于栈第二层的 Activity 将被激活,上浮到栈顶。当新的 Activity 启动入栈时,原 Activity 会被压入到栈的第二层。一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。

Activity 的 Intent Filter

##

1
2
3
4
<intent-filter > 
<action android:name="android.intent.action.MAIN" />
<action android:name="com.zy.myaction" />
</intent-filter>

Android 中关于视频检索

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
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
public List<VideoInfo> getVideoList() {
List<VideoInfo> videoInfoList = new ArrayList<>();
String[] thumbColumn = {
MediaStore.Video.Thumbnails.DATA,
MediaStore.Video.Thumbnails.VIDEO_ID
};
String[] mediaColumns = {
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DATA,
MediaStore.Video.Media.TITLE,
MediaStore.Video.Media.MIME_TYPE,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.HEIGHT,
MediaStore.Video.Media.WIDTH
};
Cursor cursor = mContext.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, mediaColumns, null, null, null);
if (cursor == null) {
ToastUtil.showMessage("没有找到相关视频文件");
return null;
}
if (cursor.moveToFirst()) {
do {
VideoInfo videoInfo = new VideoInfo();
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media._ID));
Cursor thumbCursor = mContext.getContentResolver().query(MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI
, thumbColumn, MediaStore.Video.Thumbnails.VIDEO_ID + "=" + id, null, null);
if (thumbCursor.moveToFirst()) {
String thumb = thumbCursor.getString(thumbCursor.getColumnIndex(MediaStore.Video.Thumbnails.DATA));
videoInfo.thumbPath = thumb;
}
videoInfo.id = id;
videoInfo.path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
videoInfo.title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE));
videoInfo.duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));
mmr.setDataSource(videoInfo.path);
videoInfo.height = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
videoInfo.width = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
videoInfoList.add(videoInfo);
} while (cursor.moveToNext());
}
cursor.close();
return videoInfoList;
}

#存在的问题

##有些手机媒体哭保存视频列表会有缩略图而有些则没有。但是一我们呈现的是要有封面图也就是缩略图,那该如何获取缩略图呢?
我们可以这样,先判断手机里面是否有缩略图,也就是上面检索的结果是否有thumbCusor.getCounnt()`0;没有的话:

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
private void setThumb() {
Task.callInBackground(new Callable<Object>() {
@Override
public Object call() throws Exception {
getThumbBitmap();
return null;
}
}).onSuccessTask(new Continuation<Object, Task<Object>>() {
@Override
public Task<Object> then(Task<Object> task) throws Exception {
mVideoInfoAdapter.notifyDataSetChanged();
return null;
}
});

}

//有些机型没有缩略图,需要生成bitmap
private void getThumbBitmap() {
for (VideoInfo mVideoInfo : mVideoInfoAdapter.getAll()) {
if(TextUtil.isEmptyOrNull(mVideoInfo.thumbPath) || !new File(mVideoInfo.thumbPath).exists()){
mVideoInfo.thumbBitmab = MediaStore.Video.Thumbnails.getThumbnail(getActivity().getContentResolver(), mVideoInfo.id, MediaStore.Video.Thumbnails.MINI_KIND, null);
}
}
}

###存在的问题是:每次都要去生成缩略图,每次都很耗时 待优化;
参考文章:http://jayfeng.com/2016/03/16/%E7%90%86%E8%A7%A3ThumbnailUtils/

Android PreferenceFragment的用法

简介

使用来定义App 关于 界面的一个Fragment,将条目定义在xml中,可直接跳转Activity

用法

xml定义

1
2
3
4
5
6
7
8
9
10
11
<PreferenceCategory android:title="就看天气">
<Preference
android:key="introduction"
android:title="应用介绍"/>
<Preference
android:key="current_version"
android:title="当前版本"/>
<Preference
android:key="check_version"
android:title="检查更新"/>
</PreferenceCategory>

1
2
3
4
5
public void onCreate(Bundle savedInstanceState){
addPreferencesFromResource(R.xml.about);
mIntroduction = findPreference(INTRODUCTION);
mIntroduction.setOnPreferenceClickListener(this);
}

按钮点击事件

1
2
3
4
5
 @Override public boolean onPreferenceClick(Preference preference) {
if(mShare == preference){

}
}

Android 消息机制Handle-Looper-Message

Handler

Handler这个类可以说是消息的处理者他有几个个构造函数:

  1. publicHandler()
  2. publicHandler(Callbackcallback)
  3. publicHandler(Looperlooper)
  4. publicHandler(Looperlooper, Callbackcallback)
1
2
3
//实现这个函数来接收消息
public void handleMessage(Message msg) {
}

能够处理Message,我们有两种办法:

  1. 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
  2. 无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法
    所以一般的用法就是:
    ``bash
    Handler handler=new Handler(Looper.myLooper){
    handleMassage(Message obj){

}
};
``

Looper 循环者

Looper字面上意思就是循环者,我们知道MessageQueue就是一个消息队列,就是存储Message的地方,一个队列摆在那只是静止的,那么怎么让他动起来发挥它原本应该有的作用呢。这时Looper就出来了,它可以说睡是一个动力让队列动起来!

1
2
3
4
5
6
7
8
9
10
11
12
13
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();

mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}

将当前线程初始化为一个Looper,源码如下:

1
2
3
4
5
6
7
8
9
10
public static void prepare() {
prepare(true);
}

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

我们在用的时候应该是:在run方法里面,调用prepare()和loop()方法;
一个线程只能有一个Looper。

Message

顾名思义 消息,就是传递的对象

Bolts

Download

##

1
2
3
4
dependencies {
compile 'com.parse.bolts:bolts-tasks:1.4.0'
compile 'com.parse.bolts:bolts-applinks:1.4.0'
}

简介

Bolts是异步执行的第三方库,顺序结构。采用嵌套的方式,往上回调

用法

1定义一个任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Task<Void> uploadImages(final Post post, List<Image> images) {
Task<Void> ret = Task.forResult(null);
for (final Image image : images) {
ret = ret.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
if (post.mPublishingStatus == PublishingStatus.CANCELED) {
//取消发布。
return Task.forError(new CancelledException());
}
return uploadImage(image);
}
});
}
return ret;
}

2 任务的嵌套
onSuccessTask continueWithTask