Android顶部弹出提示的两种实现方式

先给大家上一张效果图:

%title插图%num

越来越多的APP提示越来越花哨,有中间的,有顶部的,有底部的,滑动滑出的,淡入淡出的,今天就先给大家做一个简单的顶部弹出提示效果

其实这是一个很简单的功能,做起来也并不复杂,我们先看使用Toast如何实现

*种:Toast实现

布局文件layout_toast:

<?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=”55dp”
android:background=”#ff4959″
android:fitsSystemWindows=”true”
android:orientation=”vertical”>

<LinearLayout
android:id=”@+id/test”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<TextView
android:id=”@+id/txtToastMessage”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:gravity=”center”
android:text=”测试测试”
android:textColor=”#ffffff”
android:textSize=”18sp” />
</LinearLayout>

</LinearLayout>
就一个简单的文本嵌套,就不多说了

然后我们再看Java代码实现

if (mToast == null) {
mToast = new Toast(mContext);
}
mToast.setDuration(Toast.LENGTH_SHORT);
mToast.setGravity(Gravity.TOP, 0, 0);
View toastView = LayoutInflater.from(mContext).inflate(R.layout.layout_toast, null);
mToast.setView(toastView);
mToast.show();
运行程序我们发现并没有我们想要的效果,而是在顶部自适应显示

%title插图%num

我们已经setView了但是并没有根据我们的布局显示我们想要的效果,原因是什么呢?通过看源码我们发现Toas并没有设置宽高的方法,而且其宽高是根据内容的大小而自适应的,所以只能自己写了。

既然没有设置宽高的方法, 那我们是否可以通过setView(View)方法动态定义View的宽高来实现呢,想到就要做,于是我这样写

WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
View toastView = LayoutInflater.from(mContext).inflate(R.layout.layout_toast, null);
if (mToast == null) {
mToast = new Toast(mContext);
}
LinearLayout relativeLayout = (LinearLayout) toastView.findViewById(R.id.test);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(wm
.getDefaultDisplay().getWidth(), dip2px(mContext, 60));
relativeLayout.setLayoutParams(layoutParams);
mToast.setDuration(Toast.LENGTH_SHORT);
mToast.setGravity(Gravity.TOP, 0, 0);
mToast.setView(toastView);
mToast.show();
果然动态设置宽高后效果就实现了,但是这样有一个问题需要大家注意下

不能设置布局文件的根节点的宽高度,这一样无效,因此需要设置LinearLayout的宽高度
不能设置布局文件的根节点的宽高度,这一样无效,因此需要设置LinearLayout的宽高度
不能设置布局文件的根节点的宽高度,这一样无效,因此需要设置LinearLayout的宽高度
所以我在Textview上面包了一层

第二种:Popwindow实现

相对于Toast来说Popwindow实现起来就方便多了,我们只要有布局文件,只需要设置一下就可以实现跟上个相同的效果,关键代码如下:

// 把View添加到PopWindow中
this.setContentView(mPopWindow);
//设置SelectPicPopupWindow弹出窗体的宽
this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
//设置SelectPicPopupWindow弹出窗体的高
this.setHeight(dip2px(mContext, 60));
// 设置SelectPicPopupWindow弹出窗体可点击
this.setFocusable(false);
// 设置背景透明
this.setBackgroundDrawable(new ColorDrawable(0x00000000));
点击测试:

popUtil = new PopUtil(MainActivity.this, “我是POP测试”);
new CountDownTimer(2000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
popUtil.showAtLocation(MainActivity.this.findViewById(R.id.pop),
Gravity.TOP, 0, 0);
}

@Override
public void onFinish() {
popUtil.dismiss();
}
}.start();
我这里做了一个两秒的倒计时,两秒后自动取消PopWindow,跟Toast功能相同

结语:

其实延伸开还有很多效果可以实现,例如淡入淡出的动画效果,这就需要大家去开动脑筋去实现了

Demo地址  https://github.com/cuiyongtao/MyToast

Android全局异常捕获并弹窗提示

Android 难免有崩溃的时候,但是崩溃了该如何处理呢?虽然那天有位同仁说 “既然崩溃了,用户体验就差了,心里会想这是毛APP,下次也不想用了” ,所以检查BUG以防崩溃是必须的,但是也需要一个后备方案,崩溃了能友好些,我们也能收集一些崩溃的信息。
说到全局捕获异常的UncaughtExceptionHandler,就不得不说期间遇到的各种坑:
1. 初始化肯定在Application,网上说的Activity启各种不认同。但在Application启就存在不能弹AlertDialog的问题(目前不确定,不知道是自己哪里没处理好还是的确是这个问题,有时间再验证一下)
2. 崩溃不一定是单次,在多层Activity中,崩溃一个顶层的Activity可能导致下层的Activity连续崩溃,所以uncaughtException可能会捕获到多次崩溃信息(具体影响后面会说到)
先来张崩溃后的效果图:
背景是另一个APP,当前的APP已崩溃并弹出该提示

%title插图%num
实现流程:
写个类继承于UncaughtExceptionHandler,实现方法
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处
mDefaultHandler.uncaughtException(thread, ex);
} else {
// 跳转到崩溃提示Activity
Intent intent = new Intent(mContext, CrashDialog.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
System.exit(0);// 关闭已奔溃的app进程
}
}

然后转handleException方法处理异常:
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}

// 收集错误信息
getCrashInfo(ex);

return true;
}

上面的代码很清楚了,如果异常被捕获到并且异常信息不会NULL,处理完则跳转到CrashDialog。为什么跳Activity用Dialog样式,而不直接弹AlertDialog,是因为的确弹不出来。
收集错误信息:
private void getCrashInfo(Throwable ex) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String errorMessage = writer.toString();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String mFilePath = Environment.getExternalStorageDirectory() + “/” + App.ERROR_FILENAME;
FileTxt.WirteTxt(mFilePath, FileTxt.ReadTxt(mFilePath) + ‘\n’ + errorMessage);
} else {
Log.i(App.TAG, “哦豁,说好的SD呢…”);
}
}

是的,我把错误信息写到了存储并在刚才的CrashDialog中读取。为什么不直接传值呢?因为刚说到的坑第2条,多次崩溃的情况下,将导致直接传值只会传*后一次崩溃信息,而*后一次崩溃信息并不是主要引发崩溃的点,收集上来的错误信息可读性不大。那为什么我不写个全局变量来存储呢?因为尝试过,不知道是机型问题(Huawei Mate7 – API 23)还是全部问题,变量压根就不记录数据,*后只有将信息依次写到存储。
主要代码
App.java
package cn.qson.androidcrash;

/**
* @author x024
*/

import android.app.Application;

public class App extends Application {

public final static String TAG = “x024”;
public final static String ERROR_FILENAME = “x024_error.log”;

@Override
public void onCreate() {
super.onCreate();

CrashHanlder.getInstance().init(this);

}
}

CrashHanlder.java
package cn.qson.androidcrash;

/**
* @author x024
*/

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;

import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;

/**
* 收集错误报告并上传到服务器
*
* @author x024
*
*/
public class CrashHanlder implements UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler mDefaultHandler;
// CrashHandler实例
private static CrashHanlder INSTANCE = new CrashHanlder();
// 程序的Context对象
private Context mContext;

private CrashHanlder() {
}

public static CrashHanlder getInstance() {
return INSTANCE;
}

/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context;
// 获取系统默认的UncaughtException处理
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置该CrashHandler为程序的默认处理
Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
* 异常捕获
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处
mDefaultHandler.uncaughtException(thread, ex);
} else {
// 跳转到崩溃提示Activity
Intent intent = new Intent(mContext, CrashDialog.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
System.exit(0);// 关闭已奔溃的app进程
}
}

/**
* 自定义错误捕获
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}

// 收集错误信息
getCrashInfo(ex);

return true;
}

/**
* 收集错误信息
*
* @param ex
*/
private void getCrashInfo(Throwable ex) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String errorMessage = writer.toString();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String mFilePath = Environment.getExternalStorageDirectory() + “/” + App.ERROR_FILENAME;
FileTxt.WirteTxt(mFilePath, FileTxt.ReadTxt(mFilePath) + ‘\n’ + errorMessage);
} else {
Log.i(App.TAG, “哦豁,说好的SD呢…”);
}

}

}

CrashDialog.java
package cn.qson.androidcrash;

/**
* @author x024
*/

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class CrashDialog extends Activity {

private String mFilePath;
private Button btnExit, btnRestart;
private Boolean StorageState = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash);
CrashDialog.this.setFinishOnTouchOutside(false);
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
mFilePath = Environment.getExternalStorageDirectory() + “/” + App.ERROR_FILENAME;
StorageState = true;
} else {
Log.i(App.TAG, “哦豁,说好的SD呢…”);
}

new Thread(upLog).start();
initView();
}

private void initView() {
btnExit = (Button) findViewById(R.id.cash_exit);
btnRestart = (Button) findViewById(R.id.cash_restart);

btnExit.setOnClickListener(mOnClick);
btnRestart.setOnClickListener(mOnClick);

}

OnClickListener mOnClick = new OnClickListener() {

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.cash_exit:
exit();
break;
case R.id.cash_restart:
restart();
break;
default:
break;
}
}
};

// 上传错误信息
Runnable upLog = new Runnable() {
@Override
public void run() {
try {

String Mobile = Build.MODEL;
String maxMemory = “” + getmem_TOLAL() / 1024 + “m”;
String nowMemory = “” + getmem_UNUSED(CrashDialog.this) / 1024 + “m”;
String eMessage = “未获取到错误信息”;
if (StorageState) {
eMessage = FileTxt.ReadTxt(mFilePath).replace(“‘”, “”);
}
Log.i(App.TAG, “Mobile:” + Mobile + ” | maxMemory:” + maxMemory + ” |nowMemory:” + nowMemory
+ ” |eMessage:” + eMessage);

/**
* 可以在这调你自己的接口上传信息
*/
} catch (Exception e) {
e.printStackTrace();
}
}
};

private void exit() {
FileTxt.deleteFile(mFilePath);
System.exit(0);
android.os.Process.killProcess(android.os.Process.myPid());
}

private void restart() {
Intent intent = getBaseContext().getPackageManager()
.getLaunchIntentForPackage(getBaseContext().getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
exit();
}

@Override
public void onBackPressed() {
super.onBackPressed();
exit();
}

// 获取可用内存
public static long getmem_UNUSED(Context mContext) {
long MEM_UNUSED;
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);

MEM_UNUSED = mi.availMem / 1024;
return MEM_UNUSED;
}

// 获取剩余内存
public static long getmem_TOLAL() {
long mTotal;
String path = “/proc/meminfo”;
String content = null;
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path), 8);
String line;
if ((line = br.readLine()) != null) {
content = line;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
int begin = content.indexOf(‘:’);
int end = content.indexOf(‘k’);

content = content.substring(begin + 1, end).trim();
mTotal = Integer.parseInt(content);
return mTotal;
}

}

完整代码:http://download.csdn.net/detail/hx7013/9710757