Hexo

今天又是充满希望的一天

最近工作需要,用到各式各样的提示框,寻找了很久,发现一个的第三方的插件很好用,各种样式、接口良好、允许自定义。

官网:http://layer.layui.com/

使用需要先引入jq1.8以上:

1
2
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script src="layer/layer.js"></script>

 

在实际应用中,我们可能会遇到把ListView放到ScrollView中的情况,在这种情况下,ListView的滑动属性与ScrollView的滑动出现冲突,从而ListView只显示一项。这里提供一种解决方案,就是设置ListView中每个Item的高度显示之,使他失去滑动的属性,从而适应ScrollView的滑动。只需在填充数据之后设置即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 通过设置它的高度,让它“放弃”自身滚动属性,而放置到ScrollView中使用的。
*
* @param lv
*/
private void setListViewHeight(ListView lv) {
ListAdapter la = lv.getAdapter();
if (null == la) {
return;
}
// calculate height of all items.
int h = 0;
final int cnt = la.getCount();
for (int i = 0; i < cnt; i++) {
View item = la.getView(i, null, lv);
item.measure(0, 0);
h += item.getMeasuredHeight();
}
// reset ListView height
ViewGroup.LayoutParams lp = lv.getLayoutParams();
lp.height = h + (lv.getDividerHeight() * (cnt - 1));
lv.setLayoutParams(lp);
}

 

今天来介绍图片加载的框架Android-Universal-Image-Loader

GITHUB上的下载路径为:https://github.com/nostra13/Android-Universal-Image-Loader

也可以自行百度下载。

首先来封装的一个类CacheTool ,由于其他加载图片的方法有点繁琐,所以这里仅封装了一个简单实用的加载方法:

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.os.Environment;
import android.widget.ImageView;

import java.io.File;

import com.ncct.app.R;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;

/**
* 图片加载框架
*
* @author jiang
*
*/
public class CacheTool {

private static File cacheDir = Environment.getDataDirectory();
private static DisplayImageOptions options = new DisplayImageOptions.Builder().showStubImage(R.drawable.loading_img)
.showImageForEmptyUri(R.drawable.loading_error).showImageOnFail(R.drawable.loading_error)
.cacheInMemory(true).cacheOnDisc(true).bitmapConfig(Bitmap.Config.RGB_565).build();

private static ImageLoaderConfiguration config;

public static void Init(Context context) {
config = new ImageLoaderConfiguration.Builder(context).memoryCacheExtraOptions(480, 800) // max
// width,
// max
// height,即保存的每个缓存文件的最大长宽
.discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null) // Can
// slow
// ImageLoader,
// use
// it
// carefully
// (Better
// don't
// use
// it)/设置缓存的详细信息,最好不要设置这个
.threadPoolSize(3)// 线程池内加载的数量
.threadPriority(Thread.NORM_PRIORITY - 2).denyCacheImageMultipleSizesInMemory()
.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // You
// can
// pass
// your
// own
// memory
// cache
// implementation/你可以通过自己的内存缓存实现
.memoryCacheSize(2 * 1024 * 1024).discCacheSize(50 * 1024 * 1024)
.discCacheFileNameGenerator(new Md5FileNameGenerator())// 将保存的时候的URI名称用MD5
// 加密
.tasksProcessingOrder(QueueProcessingType.LIFO).discCacheFileCount(100) // 缓存的文件数量
// .discCache(new UnlimitedDiscCache(cacheDir))// 自定义缓存路径
.defaultDisplayImageOptions(DisplayImageOptions.createSimple())
.imageDownloader(new BaseImageDownloader(context, 5 * 1000, 30 * 1000)) // connectTimeout
// (5
// s),
// readTimeout
// (30
// s)超时时间
.writeDebugLogs() // Remove for release app
.build();// 开始构建
// Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(config);
}

/**
* 加载图片并监听回调结果
*
* @param iv
* @param url
* @param mImageLoadingListener
*/
public static void displayImg(ImageView iv, String url, ImageLoadingListener mImageLoadingListener) {
ImageLoader.getInstance().displayImage(url, iv, options, mImageLoadingListener);
}

/**
* 加载图片
*
* @param iv
* @param url
*/
public static void displayImg(ImageView iv, String url) {

ImageLoader.getInstance().displayImage(url, iv, options);
}

/**
* 清除内存
*/
public static void clearMemoryCache() {
ImageLoader.getInstance().clearMemoryCache();
}

/**
* 清除缓存
*/
public static void clearDiskCache() {
ImageLoader.getInstance().clearDiscCache();
}

/**
* 得到某个图片的缓存路径
*
* @param imageUrl
* @return
*/
public static String getImagePath(String imageUrl) {
return ImageLoader.getInstance().getDiscCache().get(imageUrl).getPath();
}

}

 

封装好了,里面都有详细的介绍,这里介绍下上面的中的ImageLoadingListener 接口回调,按ctrl + 鼠标左键可以进入jar包里的java文件:

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
/*******************************************************************************
* Copyright 2011-2013 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.nostra13.universalimageloader.core.assist;

import android.graphics.Bitmap;
import android.view.View;

/**
* Listener for image loading process.<br />
* You can use {@link SimpleImageLoadingListener} for implementing only needed methods.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see SimpleImageLoadingListener
* @see FailReason
* @since 1.0.0
*/
public interface ImageLoadingListener {

/**
* Is called when image loading task was started
*
* @param imageUri Loading image URI
* @param view View for image
*/
void onLoadingStarted(String imageUri, View view);

/**
* Is called when an error was occurred during image loading
*
* @param imageUri Loading image URI
* @param view View for image. Can be <b>null</b>.
* @param failReason {@linkplain FailReason The reason} why image loading was failed
*/
void onLoadingFailed(String imageUri, View view, FailReason failReason);

/**
* Is called when image is loaded successfully (and displayed in View if one was specified)
*
* @param imageUri Loaded image URI
* @param view View for image. Can be <b>null</b>.
* @param loadedImage Bitmap of loaded and decoded image
*/
void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);

/**
* Is called when image loading task was cancelled because View for image was reused in newer task
*
* @param imageUri Loading image URI
* @param view View for image. Can be <b>null</b>.
*/
void onLoadingCancelled(String imageUri, View view);
}

从以上代码中我们可以了解到接口中我们可以监听到开始、失败、完成、取消的动作。

现在开始使用吧:

1
2
3
4
5
6
7
8
9
10
private ImageView My_Head;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.personcenter);
My_Head = (ImageView) findViewById(R.id.My_Head);
String Url = "http://pic.nipic.com/2007-11-09/200711912453162_2.jpg";
CacheTool.displayImg(My_Head , Url );
}

 Mark一下,暂存一个直接通过URL获取bitmap的函数,未作内存处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 获取指定路径的图片
*
* @param urlpath
* @return
* @throws Exception
*/
public Bitmap getImage(String urlpath) throws Exception {
URL url = new URL(urlpath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
Bitmap bitmap = null;
InputStream inputStream = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}

 

本篇来介绍移动应用图片存储的第三方,官方网址:http://www.qiniu.com/

首先,在七牛上要注册开发者账号,需要认证身份证信息。认证完之后,我们来开始进行安卓端的开发。

一、准备信息

进行图片的上传与下载,我们需要准备几个信息,首先是空间名,然后是域名,再者是密钥对。如下图:

二、安卓端图片上传

首先我们需要导入JAR包,网址:http://developer.qiniu.com/docs/v6/sdk/android-sdk.html

现在来编写上传相关的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private volatile boolean isCancelled = false;// 取消标志       static String AccessKey =七牛上的公钥;
static String SecretKey =七牛上的私钥;
UploadManager uploadManager;
/**
*uploadManager有两种初始化方法,一种是默认配置,一种的自动逸配置
*/
// 默认配置
uploadManager=new UploadManager();
//自定义配置
Configuration config= new Configuration.Builder().chunkSize(256 * 1024) // 分片上传时,每片的大小。
// 默认
// 256K
.putThreshhold(512 * 1024) // 启用分片上传阀值。默认 512K
.connectTimeout(10) // 链接超时。默认 10秒
.responseTimeout(60) // 服务器响应超时。默认 60秒
// .recorder(recorder) // recorder 分片上传时,已上传片记录器。默认 null
// .recorder(recorder, keyGen) // keyGen
// 分片上传时,生成标识符,用于片记录器区分是那个文件的上传记录
.build();
// 重用 uploadManager。一般地,只需要创建一个 uploadManager 对象
uploadManager = new UploadManager(config);

初始化配置搞定了,下面来进行上传操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
String _uploadToken = getUploadToken(scope);      uploadManager.put(SAVE_FILE_DIRECTORY, // 本地上传路径、文件、字符串
key, // 网站上的名字
_uploadToken, // 验证信息
new UpCompletionHandler() {
@Override
public void complete(String key, ResponseInfo info, JSONObject response) {
Log.e("qiniu", info.toString());
Log.i("是否成功", String.valueOf(info.isOK()));
}
},
new UploadOptions(null, // 自定义变量,key必须以 x: 开始。
"webp", // 指定文件的mimeType。
false, // 是否验证上传文件。
new UpProgressHandler() { // 上传进度回调。
public void progress(String key, double percent) {
Log.i("qiniu", key + ": " + percent);
}
}, new UpCancellationSignal() { // 取消上传,当isCancelled()返回true时,不再执行更多上传。
public boolean isCancelled() {
return isCancelled;
}
})
);

在上传的操作中唯一需要我们头疼的就是_uploadToken,下面来介绍如何生成一个_uploadToken令牌信息:

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
public String getUploadToken(String scope) {
// 构造上传策略
JSONObject json = new JSONObject();
long dataline = System.currentTimeMillis() / 1000 + 3600;
String uploadToken = null;
try {
json.put("deadline", dataline);// 有效时间为一个小时
json.put("scope", scope);// 上传空间
String encodedPutPolicy = UrlSafeBase64.encodeToString(json.toString().getBytes());
byte[] sign = HmacSHA1Encrypt(encodedPutPolicy, SecretKey);
String encodedSign = UrlSafeBase64.encodeToString(sign);
uploadToken = AccessKey + ':' + encodedSign + ':' + encodedPutPolicy;// 登录信息token=公钥:加密的json信息:原始json信息
} catch (JSONException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return uploadToken;
}

/////////////// ************************************/////////////////////////
/**
* 使用 HMAC-SHA1 签名方法对对encryptText进行签名
*
* @param encryptText
* 被签名的字符串
* @param encryptKey
* 密钥
* @return
* @throws Exception
*/
private static final String MAC_NAME = "HmacSHA1";
private static final String ENCODING = "UTF-8";

private byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
byte[] data = encryptKey.getBytes(ENCODING);
// 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
// 生成一个指定 Mac 算法 的 Mac 对象
Mac mac = Mac.getInstance(MAC_NAME);
// 用给定密钥初始化 Mac 对象
mac.init(secretKey);
byte[] text = encryptText.getBytes(ENCODING);
// 完成 Mac 操作
return mac.doFinal(text);
}

好了现在我们就可以成功上传了。

三、编写其他操作

1、取消操作

1
2
3
4
5
6
/**
* 点击取消按钮,让 UpCancellationSignal#isCancelled() 方法返回 true ,以停止上传
*/
public void cancell() {
isCancelled = true;
}

2、删除操作

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
/**
* 删除指定空间、名称的资源
*
* @param bucket 空间名
* @param fileName 文件名
* @return
*/
public boolean deleteFile(String bucket, String fileName) {
try {
String entryUrl = bucket + ":" + fileName;
String encodedEntryURI = UrlSafeBase64.encodeToString(entryUrl.getBytes());
String host = "http://rs.qiniu.com";
String path = "/delete/" + encodedEntryURI;
String url = host + path;
byte[] sign = HmacSHA1Encrypt(path + "\n", SecretKey);
String encodedSign = UrlSafeBase64.encodeToString(sign);
String authorization = AccessKey + ':' + encodedSign;
HttpClient httpClient = new DefaultHttpClient();
HttpPost mothod = new HttpPost(url);
mothod.setHeader("Content-Type", "application/x-www-form-urlencoded");
mothod.setHeader("Authorization", "QBox " + authorization);
// 连接超时时间
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 20000);
// 读取超时时间
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000);
HttpResponse response = httpClient.execute(mothod);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
Log.e("删除结果", "successful");
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
Log.e("删除结果", "false");
return false;
}

好了,暂时就先到这里,虽然进行了一些封装,但是对自己的封装不很满意所以暂时不发了,以后封装完善了再发吧,这些已经足够新手理解了。下篇介绍获取网络图片并显示。主要用到目前最好的图片加载框架ImageLoad。

原帖地址:http://my.oschina.net/wangjunhe/blog/99764

创建新的控件:

作为一个有创意的开发者,你经常会遇到安卓原生控件无法满足你的需求。

为了优化你的界面和工作流程,安卓允许你去继承已经存在的控件或者实现你自己的控件。  

那么最好的方式去创建一个新的控件是什么?  这主要取决你想要完成什么。

1.有些基本功能原生控件都能提供,所以这个时候你只需要继承并对控件进行扩展。通过重写它的事件,onDraw,但是始终都保持都父类方法的调用。

2.组合控件 就是通过合并几个控件的功能来生成一个控件。

3.完完整整创建一个新的控件。

1.修改存在的控件

例子:

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
    public class MyTextView extends TextView {

public MyTextView(Context context, AttributeSet ats, int defStyle) {
super(context, ats, defStyle);
}

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

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

@Override
public void onDraw(Canvas canvas) {
// 在画布上画文本之下的内容

// 保证默认的文本渲染
super.onDraw(canvas);

// 在画布上画文本之上的内容

}

@Override
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
// 写自己的控制

// 保持父类默认的控制
return super.onKeyDown(keyCode, keyEvent);
}
}

2.组合控件

1.最简单的方式,是定义了XML布局文件,然后用include实现重用。(。。。这也算啊。。。)

2.去合并一个控件 通常你自定义的控件需要继承一个ViewGroup(通常就是Layout),就像:

1
2
3
4
5
6
7
8
9
    public class MyCompoundView extends LinearLayout {
public MyCompoundView(Context context) {
super(context);
}

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

      就像activity,比较好的设计一个混合的控件UI布局是使用一个外部的layout资源。

      这里我们模拟定义一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version=”1.0” encoding=”utf-8”?> 
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>
<EditText
android:id=”@+id/editText”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
/>
<Button
android:id=”@+id/clearButton”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Clear”
/>
</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
    public class ClearableEditText extends LinearLayout {

EditText editText;
Button clearButton;

public ClearableEditText(Context context) {
super(context);

// Inflate the view from the layout resource.
String infService = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li;

li = (LayoutInflater) getContext().getSystemService(infService);
/*这句很关键,解析反射资源文件,然后将布局附加到当前的控件,也就是this*/
li.inflate(R.layout.clearable_edit_text, this, true);

/* 因为反射成功后的布局已经附加上了,那么直接可以findViewById*/
editText = (EditText) findViewById(R.id.editText);
clearButton = (Button) findViewById(R.id.clearButton);

// 下面自定义的方法就是为控件注册监听,不解释了
hookupButton();
}
}

使用:在activity_main.xml

 

<com.example.customview.MyCompoundView android:layout_width=”match_parent” android:layout_height=”wrap_content” />

3. 完完全全自定义控件

通常是继承View或者SurfaceView ,View类提供一个Canvas(画布)和一系列的画的方法,还有Paint(画笔)。使用它们去创建一个自定义的UI。你可以重写事件,包括屏幕接触或者按键按下等等,用来提供与用户交互。

1.如果你不需要快速重画和3D图像的效果,那么让View作为父类提供一个轻量级的解决方案。

2.如若不然,就需要使用SurfaceView作为父类,这样你就可以提供一个后台线程去画和使用OPENGL去实现你的图像。这个就相对重量级了,如果你的视图需要经常更新,然后由需要显示比较复杂的图像信息(尤其是在游戏和3D可视化),SurfaceView将是更好的选择。

在这里我们讨论前者,后者后期再讨论。

 一般你需要重写2个方法: 
1.onMeasure

什么是onMeasure?

下面转载一段文章:

View在屏幕上显示出来要先经过measure(计算)和layout(布局). 
1、什么时候调用onMeasure方法?  
当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec. 
这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.

更好的方法是你传递View的高度和宽度到setMeasuredDimension方法里,这样可以直接告诉父控件,需要多大地方放置子控件.

widthMeasureSpec和heightMeasureSpec这2个参数都是整形是出于效率的考虑,所以经常要做的就是对其解码=>

1
2
1. int specMode = MeasureSpec.getMode(measureSpec);
1. int specSize = MeasureSpec.getSize(measureSpec);

  

  1. 依据specMode的值,(MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST)

  2. 如果是AT_MOST,specSize 代表的是最大可获得的空间; 
    如果是EXACTLY,specSize 代表的是精确的尺寸; 
    如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。

 

2、那么这些模式和我们平时设置的layout参数fill_parent, wrap_content有什么关系呢? 
经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子 view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。 
而当设置为 wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式表示你没有指定大小。

  1. View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。  
    有个观念需要纠正的是,fill_parent应该是子view会占据剩下容器的空间,而不会覆盖前面已布局好的其他view空间,当然后面布局子 view就没有空间给分配了,所以fill_parent属性对布局顺序很重要。以前所想的是把所有容器的空间都占满了,难怪google在2.2版本里 把fill_parent的名字改为match_parent. 
    在两种情况下,你必须绝对的处理这些限制。在一些情况下,它可能会返回超出这些限制的尺寸,在这种情况下,你可以让父元素选择如何对待超出的View,使用裁剪还是滚动等技术。 
    接下来的框架代码给出了处理View测量的典型实现:
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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredHeight = measureHeight(heightMeasureSpec);
int measuredWidth = measureWidth(widthMeasureSpec);

setMeasuredDimension(measuredHeight, measuredWidth); // 记住这句可不能省。
}

private int measureHeight(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

// Default size if no limits are specified.
int result = 500;

if (specMode == MeasureSpec.AT_MOST) {
// Calculate the ideal size of your
// control within this maximum size.
// If your control fills the available
// space return the outer bound.
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
// If your control can fit within these bounds return that value.
result = specSize;
}
return result;
}

private int measureWidth(int measureSpec) {
// 代码基本类似measureHeight
}

总结:

通过

1
2
int specMode = MeasureSpec.getMode(measureSpec); 
int specSize = MeasureSpec.getSize(measureSpec);

这2个值,然后计算自己想要占有的宽和高。

2.onDraw

这个不解释了。(后期会细说canvas和paint)

各种数字类型转换成字符串型:
String s = String.valueOf(value); // 其中 value 为任意一种数字类型。
字符串型转换成各种数字类型:

1
2
3
4
5
6
7
String s = "169";
byte b = Byte.parseByte( s );
short t = Short.parseShort( s );
int i = Integer.parseInt( s );
long l = Long.parseLong( s );
Float f = Float.parseFloat( s );
Double d = Double.parseDouble( s );

实现了分页的滑动效果,做的demo流畅运行

注:貌似支持的样式(控件)有一定的限制,我试过短信的listview页面,暂无法实现滑动效果

java文件:MainActivity.java、Activity1.java、Activity2.java、Activity3.java、Activity4.java

MainActivity.java

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package com.example.tabhostmove;

import android.app.Activity;
import android.app.TabActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;


public class MainActivity extends TabActivity {
private TabHost tabHost;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}


private void init() {
// TODO Auto-generated method stub

tabHost = getTabHost();
// 页面1
TabSpec spec1 = tabHost.newTabSpec("1");
spec1.setIndicator("1", getResources().getDrawable(R.drawable.ic_launcher));
Intent intent1 = new Intent(this, Activity1.class);
spec1.setContent(intent1);

// 页面2
TabSpec spec2 = tabHost.newTabSpec("2");
spec2.setIndicator("2", getResources().getDrawable(R.drawable.ic_launcher));
Intent intent2 = new Intent(this, Activity2.class);
spec2.setContent(intent2);

// 页面3
TabSpec spec3 = tabHost.newTabSpec("3");
spec3.setIndicator("3", getResources().getDrawable(R.drawable.ic_launcher));
Intent intent3 = new Intent(this, Activity3.class);
spec3.setContent(intent3);

// 页面4
TabSpec spec4 = tabHost.newTabSpec("4");
spec4.setIndicator("4", getResources().getDrawable(R.drawable.ic_launcher));
Intent intent4 = new Intent(this, Activity4.class);
spec4.setContent(intent4);

tabHost.addTab(spec1);
tabHost.addTab(spec2);
tabHost.addTab(spec3);
tabHost.addTab(spec4);

}



private GestureDetector detector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if ((e2.getRawX() - e1.getRawX()) > 80) {
showNext();
return true;
}

if ((e1.getRawX() - e2.getRawX()) > 80) {
showPre();
return true;
}
return super.onFling(e1, e2, velocityX, velocityY);
}

});


@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);
return super.onTouchEvent(event);
}

/**
* 当前页面索引
*/
int i = 0;

/**
* 显示下一个页面
*/
protected void showNext() {
// 三元表达式控制3个页面的循环.
//tabHost.setCurrentTab(i = i == 3 ? i = 0 : ++i);
//Log.i("kennet", i + "");
//四个页面的下一个循环
switch(i)
{
case 0:
i++;
tabHost.setCurrentTab(i);
break;
case 1:
i++;
tabHost.setCurrentTab(i);
break;
case 2:
i++;
tabHost.setCurrentTab(i);
break;
case 3:
i=0;
tabHost.setCurrentTab(i);
break;

}
}

/**
* 显示前一个页面
*/
protected void showPre() {
// 三元表达式控制3个页面的循环.
//tabHost.setCurrentTab(i = i == 0 ? i = 3 : --i);
//四个页面的上一个循环
switch(i)
{
case 0:
i=3;
tabHost.setCurrentTab(i);
break;
case 1:
i--;
tabHost.setCurrentTab(i);
break;
case 2:
i--;
tabHost.setCurrentTab(i);
break;
case 3:
i--;
tabHost.setCurrentTab(i);
break;

}
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

xml布局文件:activity_main.xml、activit1.xml、activit2.xml、activit3.xml、activit4.xml

activity_main.xml

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
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >
</FrameLayout>
</LinearLayout>

</TabHost>

注:activity1、2、3、4是测试的页面,随便建几个即可,别忘了在AndroidManifest.xml里注册页面的活动

实现效果:

主要的用到的控件:HorizontalScrollView

主要的功能:把几张图片解析成一张图片,在一个容器中呈现。

布局文件xml

side_bar_scollview.xml//显示view的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<HorizontalScrollView
android:id="@+id/MyScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<LinearLayout
android:id="@+id/ll_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
</LinearLayout>

</HorizontalScrollView>

</LinearLayout>

home.xml//显示的主页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/home_bg">

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主页" />

</LinearLayout>

menu.xml//显示的菜单页面

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/menu_bg">

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="菜单" />

</FrameLayout>

MainActivity.java//主活动

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
package com.example.side_bar_scrollview;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;


public class MainActivity extends Activity {

private HorizontalScrollView scrollview;
private LinearLayout view_layout;
private int width;
private int height;
private View home_view;
private View menu_view;
private float rate=0.4f;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//关联界面ID
setContentView(R.layout.side_bar_scollview);
//关联控件ID
scrollview=(HorizontalScrollView) findViewById(R.id.MyScrollView);
view_layout=(LinearLayout) findViewById(R.id.ll_layout);
//监听布局
MyLayoutListener();
//隐藏滚动条
scrollview.setHorizontalScrollBarEnabled(false);
}

/**
* 监听布局的变化
* 1.getViewTreeObserver --- view事件的观察者
* 2.addOnGlobalLayoutListener
* 当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,
* 所要调用的回调函数的接口类
*
*/
private void MyLayoutListener(){

scrollview.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {

@Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
//移除之前已经注册的全局布局的回调函数,使图片不会循环连在一起
view_layout.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
//获取最后一次调用measure()测量得到的scrollview的宽和高
height = scrollview.getMeasuredHeight();
width = scrollview.getMeasuredWidth();
//解析主页和菜单的布局
home_view=getLayoutInflater().inflate(R.layout.home,
null);
menu_view=getLayoutInflater().inflate(R.layout.menu,
null);
//添加view到view_layout
view_layout.addView(menu_view, (int)(width*rate), height);
view_layout.addView(home_view, width, height);

}
});

}

}

效果图:

 

1、clean之后R文件消失

clean之后R文件消失是因为布局的XML文件存在错误,无法编译你的资源文件,所以无法自动生成R文件,在Problem、LogCat等界面查看错误的原因,把错误改正即可;

此外还有可能是引用问题,查看开头的import是否错误的引用了android的组件,把他删掉,重新引用具体包的路径。

方法一:在AndroidManifest.xml中配置

如果不想让软件在横竖屏之间切换,最简单的办法就是在项目的AndroidManifest.xml中找到你所指定的activity中加上android:screenOrientation属性,他有以下几个参数:

“unspecified”:默认值 由系统来判断显示方向.判定的策略是和设备相关的,所以不同的设备会有不同的显示方向. 
“landscape”:横屏显示(宽比高要长) 
“portrait”:竖屏显示(高比宽要长) 
“user”:用户当前首选的方向 
“behind”:和该Activity下面的那个Activity的方向一致(在Activity堆栈中的) 
“sensor”:有物理的感应器来决定。如果用户旋转设备这屏幕会横竖屏切换。 
“nosensor”:忽略物理感应器,这样就不会随着用户旋转设备而更改了(”unspecified”设置除外)。

 

方法二:在java代码中设置

设置横屏代码:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//横屏

设置竖屏代码:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//竖屏

因为横屏有两个方向的横法,而这个设置横屏的语句,如果不是默认的横屏方向,会把已经横屏的屏幕旋转180°。

所以可以先判断是否已经为横屏了,如果不是再旋转,不会让用户觉得转的莫名其妙啦!代码如下:

if(this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT){
   setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

}

0%