转载请注明出处:http://blog.csdn.net/amd123456789/article/details/52223353
引导界面
一直想写一个与网络有交互的app来练练手,偶然发现知乎日报这个app,想着就着手模仿一个,尽管已经有人完全写了出来,但是自己不妨也写一个,全当练习。
这一篇就来写写引导页面,就是一张逐步放大的图片外加几个文本组成。效果图是这样(不会弄gif动态图):
布局文件
首先,先搭好UI界面,很简单,两个TextView+一个ImageView, 布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_guide"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
<TextView
android:id="@+id/tv_guide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="15dp"
android:textColor="@android:color/darker_gray"
android:textSize="15sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/tv_guide"
android:layout_centerHorizontal="true"
android:layout_margin="10dp"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="28sp" />
</RelativeLayout>
其次,ImageView渐变放大效果的实现,用Animation。配好Animation 的xml文件,
在目录res/anim/guide_imageview_zoom.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">//表示放大后效果不重置
<scale
android:duration="2000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.2"
android:toYScale="1.2" />
</set>
Activity主要逻辑
Activity的主体逻辑,是从服务器获取资源,设置给布局上的控件。来看看这个activity的逻辑代码。
package com.example.seaice.zhihuribao;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.seaice.zhihuribao.Utils.BaseApplication;
import com.example.seaice.zhihuribao.Utils.ThreadMgr;
import com.example.seaice.zhihuribao.Utils.UiUtils;
import com.example.seaice.zhihuribao.bean.GuideInfo;
import com.example.seaice.zhihuribao.protocol.GuideProtocol;
import com.squareup.picasso.Picasso;
import butterknife.ButterKnife;
import butterknife.InjectView;
public class MainActivity extends AppCompatActivity {
private GuideProtocol guideProtocol;//负责从网络和本地获取数据
private GuideInfo guideInfo;
@InjectView(R.id.iv_guide)
ImageView iv_guide;
@InjectView(R.id.tv_guide)
TextView tv_guide;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
guideProtocol = new GuideProtocol();
setImageGuide();
}
//从服务器加载资源
private void loadDataFromProtocol(final Runnable r){
Runnable runnable = new Runnable() {
@Override
public void run() {
guideInfo = guideProtocol.loadData();//获取数据
UiUtils.runOnUiThread(r);
}
};
ThreadMgr.getThreadPool().execute(runnable);
}
//设置imageview的放大效果
private void setImageGuide(){
Runnable r = new Runnable() {
@Override
public void run() {
//guide image view
Picasso.with(BaseApplication.getApplication()).load(guideInfo.getGuidePic()).into(iv_guide);
Animation guideImageZoomAni = AnimationUtils.loadAnimation(MainActivity.this, R.anim.guide_imageview_zoom);
iv_guide.startAnimation(guideImageZoomAni);
tv_guide.setText(guideInfo.getGuideName());
}
};
loadDataFromProtocol(r);
}
}
获取网络数据
GuideProtocol 是从网络/缓存获取json数据的一个封装类
1. 先从本地获取json数据
2. 如果为空,再从服务器上获取,并且保存到本地缓存
3. 若不为空,则直接解析json数据
以上便是这个类的主要逻辑,详细可以查看我之前的文章,三级缓存的基本原理
http://blog.csdn.net/amd123456789/article/details/52092927
package com.example.seaice.zhihuribao.protocol;
import android.util.Log;
import com.example.seaice.zhihuribao.Utils.FileUtils;
import com.example.seaice.zhihuribao.Utils.HttpUtils;
import com.example.seaice.zhihuribao.Utils.LogUtils;
import com.example.seaice.zhihuribao.bean.GuideInfo;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by seaice on 2016/8/11.
*/
public class GuideProtocol {
private static final String GUIDE_URL = "http://news-at.zhihu.com/api/4/start-image/1080*1776";
private static final String GUIDE_DIR = "guide";
private static final String TAG = "GuideProtocol";
/**
* 1.先从本地获取json数据
* 2.如果为空,再从服务器上获取
* 3.若获取到json,保存到本地
* 4.获取到json不为空,解析json数据
*/
public GuideInfo loadData() {
String json = loadDataFromLocal();
if (json == null) {
Log.e(TAG, "从服务器获取");
json = loadDataFromServer();
if (json != null) {
saveDataToLocal(json);
}
} else {
Log.e(TAG, "已经从本地获取");
}
if (json != null) {
return paserJsonData(json);
}
return null;
}
private GuideInfo paserJsonData(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
String guideName = jsonObject.getString("text");
String guidePicUrl = jsonObject.getString("img");
GuideInfo guideInfo = new GuideInfo(guideName, guidePicUrl);
Log.e(TAG, guideInfo.toString());
return guideInfo;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
//1.把整个json文件写到一个本地文件中
//2.把每条数据摘出来保存到数据库中
private String loadDataFromLocal() {
File dir = FileUtils.getCacheDir();
File file = new File(dir, GUIDE_DIR);
try {
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
long saveTime = Long.parseLong(br.readLine());
if (System.currentTimeMillis() > saveTime) {
return null;
} else {
String str;
StringWriter sw = new StringWriter();
while ((str = br.readLine()) != null) {
sw.write(str);
}
LogUtils.i("本地获取数据");
return sw.toString();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private void saveDataToLocal(String json) {
LogUtils.i("保存到本地");
//第一行写时间,然后保存起来
File dir = FileUtils.getCacheDir();
File file = new File(dir, GUIDE_DIR);
BufferedWriter bw = null;
try {
FileWriter fw = new FileWriter(file);
bw = new BufferedWriter(fw);
bw.write(System.currentTimeMillis() + 1000 * 100 + "");
bw.newLine();
bw.write(json);
bw.flush();
fw.close();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private String loadDataFromServer() {
String result = null;
Request request = new Request.Builder().url(GUIDE_URL).build();
Response response = null;
response = HttpUtils.execute(request);
if (response != null && response.isSuccessful()) {
try {
result = response.body().string();
Log.e(TAG, result);
return result;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
于此对应的GuideInfo为:
package com.example.seaice.zhihuribao.bean;
/**
* Created by seaice on 2016/8/11.
*/
public class GuideInfo {
public GuideInfo(String guideName, String guidePic) {
this.guideName = guideName;
this.guidePic = guidePic;
}
private String guideName;
private String guidePic;
public String getGuideName() {
return guideName;
}
public void setGuideName(String guideName) {
this.guideName = guideName;
}
public String getGuidePic() {
return guidePic;
}
public void setGuidePic(String guidePic) {
this.guidePic = guidePic;
}
@Override
public String toString() {
return "GuideInfo{" +
"guideName='" + guideName + '\'' +
", guidePic=" + guidePic +
'}';
}
}
工具类介绍Utils
- BaseApplication用来获取全局用的Context,主线程handler等,这个是在应用启动时便会跑,需要在清单文件中配置它。
package com.example.seaice.zhihuribao.Utils;
import android.app.Application;
import android.os.Handler;
/**
* Created by seaice on 2016/8/11.
*/
public class BaseApplication extends Application {
private static BaseApplication application;
private static int mainThreadId;
private static Handler handler;
@Override
public void onCreate(){
super.onCreate();
application = this;
mainThreadId = android.os.Process.myTid();
handler = new Handler();
}
public static BaseApplication getApplication(){
return application;
}
public static int getMainThreadId(){
return mainThreadId;
}
public static Handler getHandler(){
return handler;
}
}
- FileUtils用来获取缓存文件的路径
package com.example.seaice.zhihuribao.Utils;
import android.os.Environment;
import android.util.Log;
import java.io.File;
/**
* Created by seaice on 2016/8/11.
*/
public class FileUtils {
private static final String TAG = "FileUtils";
private static final String CACHE = "cache";
private static final String ROOT = "zhihuribao";
public static File getDir(String str){
StringBuilder path = new StringBuilder();
if(isSDAvailable()){
path.append(Environment.getExternalStorageDirectory().getAbsolutePath());
path.append(File.separator);
path.append(ROOT);
path.append(File.separator);
path.append(str);
}else{
File fileDir = BaseApplication.getApplication().getCacheDir();
path.append(fileDir.getAbsolutePath());
path.append(File.separator);
path.append(str);
}
Log.e(TAG, path.toString());
File file = new File(path.toString());
if(!file.exists() || !file.isDirectory()){
file.mkdirs();
LogUtils.i("make dirs");
}
return file;
}
public static File getCacheDir(){
return getDir(CACHE);
}
private static boolean isSDAvailable() {
return Environment.isExternalStorageEmulated();
}
}
- HttpUtils对网络相关操作的一个简单封装
package com.example.seaice.zhihuribao.Utils;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by seaice on 2016/8/11.
*/
public class HttpUtils {
private static final String TAG = "HttpUtils";
private static final OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
.readTimeout(5000, TimeUnit.MILLISECONDS).build();
public static OkHttpClient getInstance() {
return mOkHttpClient;
}
/**
* 该不会开启异步线程。
*
* @param request
* @return
* @throws IOException
*/
public static Response execute(Request request) {
try {
return getInstance().newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 开启异步线程访问网络
*
* @param request
* @param responseCallBack
*/
public static void enqueue(Request request, Callback responseCallBack) {
getInstance().newCall(request).enqueue(responseCallBack);
}
/**
* 开启异步线程访问网络, 且不在意返回结果(实现空callback)
*
* @param request
*/
public static Response enqueue(Request request) {
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
return null;
}
}
- LogUtils日志输出相关的工具类
package com.example.seaice.zhihuribao.Utils;
import android.util.Log;
/**
* Created by seaice on 2016/8/12.
*/
public class LogUtils {
private static final String TAG = "zhihuribao";
public static final int LEVEL_NONE = 0;
public static final int LEVEL_VERBOSE = 1;
public static final int LEVEL_DEBUG = 2;
public static final int LEVEL_INFO = 3;
public static final int LEVEL_WARN = 4;
public static final int LEVEL_ERROR = 5;
private static int mDebugLevel = LEVEL_ERROR;
public static void v(String msg) {
if (mDebugLevel >= LEVEL_VERBOSE) {
Log.v(TAG, msg);
}
}
public static void d(String msg) {
if (mDebugLevel >= LEVEL_DEBUG) {
Log.d(TAG, msg);
}
}
public static void i(String msg) {
if (mDebugLevel >= LEVEL_INFO) {
Log.i(TAG, msg);
}
}
public static void w(String msg) {
if (mDebugLevel >= LEVEL_WARN) {
Log.w(TAG, msg);
}
}
public static void e(String msg) {
if (mDebugLevel >= LEVEL_ERROR) {
Log.e(TAG, msg);
}
}
}
- ThreadMgr线程池相关,管理线程的开启数量,减少开销
package com.example.seaice.zhihuribao.Utils;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by seaice on 2016/8/12.
*/
public class ThreadMgr {
private static ThreadPool threadPool;
public static ThreadPool getThreadPool(){
if(threadPool == null){
synchronized (ThreadMgr.class){
if(threadPool == null){
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 0L;
threadPool = new ThreadPool(corePoolSize,maxPoolSize,keepAliveTime);
}
}
}
return threadPool;
}
public static class ThreadPool{
public static ThreadPoolExecutor executor = null;
private int corePoolSize;
private int maxPoolSize;
private long keepAliveTime = 0;
public ThreadPool(int corePoolSize, int maxPoolSize, long keepAliveTime){
this.corePoolSize = corePoolSize;
this.maxPoolSize = maxPoolSize;
this.keepAliveTime = keepAliveTime;
}
public void execute(Runnable runnable){
if(runnable == null){
return;
}
if (executor == null) {
/**
* 1.corePoolSize:里面可以开辟多少个线程
* 2.如果排队满了,额外的开的线程数
* 3.如果这个线程池没有要执行的任务,存活多久
* 4.时间单位
* 5.如果,线程池里管理的线程都已经用了,把剩下的任务都先临时存到这个对象中排队
*/
executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
executor.execute(runnable);
}
public void cancel(Runnable runnable){
if(executor!=null && !executor.isShutdown() && !executor.isTerminated()){
executor.remove(runnable);
}
}
}
}
- UiUtils和UI相关操作的工具类
package com.example.seaice.zhihuribao.Utils;
import android.content.res.Resources;
import android.util.Log;
/**
* Created by seaice on 2016/8/12.
*/
public class UiUtils {
private static final String TAG = "UiUtils";
public static Resources getResource() {
return BaseApplication.getApplication().getResources();
}
/**
* 获取字符串数组
*
* @param tabNames
* @return
*/
public static String[] getStringArray(int tabNames) {
return getResource().getStringArray(tabNames);
}
public static void runOnUiThread(Runnable runnable) {
if (runnable == null) {
return;
}
//证明在主线程
if (android.os.Process.myTid() == BaseApplication.getMainThreadId()) {
Log.e(TAG, "主线程");
runnable.run();
} else {
Log.e(TAG, "非主线程");
BaseApplication.getHandler().post(runnable);
}
}
}