开源中国安卓客户端源码之自定义控件---ScreenShotView
首先,感谢开源中国的开源精神。当初学者拿到客户端源码时,可能会对其中的项目结构和代码产生许多困惑,不知道该从何下手,当然我也是其中一员,接触安卓时间不长,也不是很精通,但是通过一段时间的琢磨,慢慢地领会到其中的一些编程方法,我只是想把我弄明白的这些知识通过博客的形式记录下来,以备以后查看,当然也可以帮助到更多的初学者。我的主要工作是给代码添加注释,理顺调用关系,让初学者更快、更深刻地理解代码的含义,领会其精神。首先大家从http://git.oschina.net/oschina/android-app下载整个开源项目,先跑通这个程序,并且对这个项目整体的结构有大致的了解,然后再看我写的博客。由于我也是初学者,错误之处在所难免,希望大家踊跃指出,你们的支持是我最大的动力,谢谢!
本文研究如下功能如何实现:
涉及到的代码:
1、net.oschina.app.widget.ScreenShotView
package net.oschina.app.widget;
import net.oschina.app.AppConfig;
import net.oschina.app.R;
import net.oschina.app.common.UIHelper;
import net.oschina.app.ui.BaseActivity;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
/**
*
* 实现截图功能的View
*
* @author [email protected]
*
*/
public class ScreenShotView extends View implements OnTouchListener {
/**
* 定义画笔
*/
private Paint mGrayPaint;
/**
* 定义截屏所用的四个矩形
*/
private Rect topRect;
private Rect rightRect;
private Rect bottomRect;
private Rect leftRect;
/**
* 设备的宽
*/
private int screenWidth;
/**
* 设备的高
*/
private int screeenHeight;
/**
* 回调模式所用的接口
*/
private OnScreenShotListener mListener;
/**
* 上下文
*/
private BaseActivity mContext;
/**
* 截图矩形的坐标
*/
private int top, right, bottom, left;
/**
* 辅助变量,用于暂时保存矩形坐标
*/
private int tmpLeft, tmpTop, tmpRight, tmpBottom;
/**
* 矩形的宽高
*/
private int innerWidth, innerHeight;
/**
* 触摸屏幕前后坐标
*/
private int downX, downY, moveX, moveY;
/**
* 选择标识(开始画截屏)
*/
private final static int TOUCH_SELECT = 1;
/**
* 移动截屏标识
*/
final static int TOUCH_MOVE = 2;
/**
* 默认为选择标识
*/
private int touchFlag = TOUCH_SELECT;
/**
* 是否在移动选区内
*/
private boolean isInMoveSelection;
/**
* 上拉调整大小
*/
private boolean isInTopPullArea;
/**
* 右拉调整大小
*/
private boolean isInRightPullArea;
/**
* 下拉调整大小
*/
private boolean isInBottomPullArea;
/**
* 左拉调整大小
*/
private boolean isInLeftPullArea;
/**
* 选框内双击中的有效点击次数
*/
private int innnerClickCount = 0;
/**
* 选框外双击的有效点击次数
*/
private int outterClickCount = 0;
/**
* 触碰事件间隔
*/
private long timeInterval;
/**
* 第一次单击时刻
*/
private long firstClickTime;
/**
* 第二次单击时刻
*/
private long secondClickTime;
/**
* 刚触碰的时刻
*/
private long TouchDownTime;
/**
* 触碰结束时刻
*/
private long TouchUpTime;
/**
* 触摸down,up一下500毫秒以内为一次单击
*/
private final static long VALID_CLICK_INTERVAL = 500;
/**
* 两次单击之间时间距离
*/
private final static long VALID_DOUBLE_CLICK_INTERNAL = 1000;
/**
* 有效拉大拉小距离
*/
private final static int VALID_PULL_DISTANCE = 30;
/**
* 是否显示双击图片
*/
private boolean isTimeToTip = false;
/**
* 截图是否隐藏
*/
private boolean isHide = false;
/**
* 截图保存路径
*/
public final static String TEMP_SHARE_FILE_NAME = AppConfig.DEFAULT_SAVE_IMAGE_PATH
+ "_share_tmp.jpg";
/**
* 显示出双击图片
*/
private static Bitmap bmDoubleClickTip;
/**
* 监听截图完成后要完成的动作
*/
public interface OnScreenShotListener {
public void onComplete(Bitmap bm);
}
@SuppressLint("NewApi")
public ScreenShotView(BaseActivity context, OnScreenShotListener listener) {
super(context);
//监听器初始化
mListener = listener;
//上下文初始化
mContext = context;
//这句话有两个作用:
//(1)当前Activity不可销毁
//(2)将当前View对象传递给基类BaseActivity中的view对象,
//在取消截屏摁返回键的时候会用到,第一次摁返回键时,消失的是自定义的view,
//第二次摁返回键时,消失的才是真正的Activity中的view
mContext.setAllowDestroy(false, this);
//获取不同设备的宽高
Point p = getDisplaySize(context.getWindowManager().getDefaultDisplay());
screenWidth = p.x;
screeenHeight = p.y;
//实例化画笔
mGrayPaint = new Paint();
//将该画笔的透明度设置为100
mGrayPaint.setARGB(100, 0, 0, 0);
//实例化四个矩形类
topRect = new Rect();
rightRect = new Rect();
bottomRect = new Rect();
leftRect = new Rect();
//将topRect的宽高设置为设备的宽高(即当点击截屏分享的时候,整个屏幕都被topRect所占据)
topRect.set(0, 0, screenWidth, screeenHeight);
//由于当前类实现了OnTouchListener接口,因此可以将当前对象赋值给自定义View的setOnTouchListener()方法
setOnTouchListener(this);
//实例化双击图标
if(bmDoubleClickTip==null){
bmDoubleClickTip = BitmapFactory.decodeStream(getResources().openRawResource(R.drawable._pointer));
}
//吐司提示信息
UIHelper.ToastMessage(mContext, "请滑动手指确定选区!");
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//在屏幕上用画笔画出这四个矩形
canvas.drawRect(topRect, mGrayPaint);
canvas.drawRect(rightRect, mGrayPaint);
canvas.drawRect(bottomRect, mGrayPaint);
canvas.drawRect(leftRect, mGrayPaint);
//判断矩形是否画完
if (isTimeToTip) {
//下面7行代码作用是让双击图片在选框中居中显示
//计算矩形的宽高
innerWidth = right - left;
innerHeight = bottom - top;
//计算双击图片的宽高
int bmWidth = bmDoubleClickTip.getWidth();
int bmHeight = bmDoubleClickTip.getHeight();
//在矩形中居中显示双击图片的坐标
int x = (int) (left + (innerWidth - bmWidth) * 0.5);
int y = (int) (top + (innerHeight - bmHeight) * 0.5);
//在矩形中显示出该图片
canvas.drawBitmap(bmDoubleClickTip, x, y, null);
//双击图片显示后,马上设置成false
isTimeToTip = false;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 重写onTouch方法
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
//手指按下的时候
case MotionEvent.ACTION_DOWN:
//双击图标不显示
isTimeToTip = false;
//记录按下时的坐标
downX = (int) event.getX();
downY = (int) event.getY();
//判断按下的坐标是否在画好的矩形内
isInMoveSelection = isInSeletion(downX, downY);
//判断按下的坐标是否属于画好的矩形内的上拉区域
isInTopPullArea = isInTopPullArea(downX, downY);
//判断按下的坐标是否属于画好的矩形内的右拉区域
isInRightPullArea = isInRightPullArea(downX, downY);
//判断按下的坐标是否属于画好的矩形内的下拉区域
isInBottomPullArea = isInBottomPullArea(downX, downY);
//判断按下的坐标是否属于画好的矩形内的左拉区域
isInLeftPullArea = isInLeftPullArea(downX, downY);
//记录当前按下屏幕的时间
TouchDownTime = System.currentTimeMillis();
break;
//手指在屏幕上移动的时候
case MotionEvent.ACTION_MOVE:
//双击图片不显示
isTimeToTip = false;
//移动过程中手指的坐标
moveX = (int) event.getX();
moveY = (int) event.getY();
if (touchFlag == TOUCH_SELECT) {
//开始画矩形
tmpLeft = left = getMin(downX, moveX);
tmpTop = top = getMin(downY, moveY);
tmpRight = right = getMax(downX, moveX);
tmpBottom = bottom = getMax(downY, moveY);
//不是在移动矩形
isInMoveSelection = false;
} else if (touchFlag == TOUCH_MOVE && isInMoveSelection) {
//移动画好的矩形
int xDistance = moveX - downX;
int yDistance = moveY - downY;
//决定矩形选框的坐标
decideCoordinate(xDistance, yDistance);
} else if (touchFlag == TOUCH_MOVE && isInTopPullArea) {
//当手指在上拉调整区域,将画好的矩形垂直拉伸
int yDistance = downY - moveY;
//下面两行代码的目的是矩形选框的top与bottom至少要有2倍于VALID_PULL_DISTANCE的距离
int extremeY = (bottom - 2 * VALID_PULL_DISTANCE);
top = (tmpTop - yDistance) < extremeY ? (tmpTop - yDistance)
: extremeY;
} else if (touchFlag == TOUCH_MOVE && isInRightPullArea) {
//当手指在右拉调整区域,将画好的矩形水平拉伸
int xDistance = moveX - downX;
//下面两行代码的目的是矩形选框的right与left至少要有2倍于VALID_PULL_DISTANCE的距离
int extremeX = (left + 2 * VALID_PULL_DISTANCE);
right = (tmpRight + xDistance) > extremeX ? (tmpRight + xDistance)
: extremeX;
} else if (touchFlag == TOUCH_MOVE && isInBottomPullArea) {
//当手指在下拉调整区域,将画好的矩形垂直拉伸
int yDistance = downY - moveY;
//下面两行代码的目的是矩形选框的bottom与top至少要有2倍于VALID_PULL_DISTANCE的距离
int extremeY = (top + 2 * VALID_PULL_DISTANCE);
bottom = (tmpBottom - yDistance) > extremeY ? (tmpBottom - yDistance)
: extremeY;
} else if (touchFlag == TOUCH_MOVE && isInLeftPullArea) {
//当手指在左拉调整区域,将画好的矩形水平拉伸
int xDistance = downX - moveX;
//下面两行代码的目的是矩形选框的left与right至少要有2倍于VALID_PULL_DISTANCE的距离
int extremeX = (right - 2 * VALID_PULL_DISTANCE);
left = (tmpLeft - xDistance) < extremeX ? (tmpLeft - xDistance)
: extremeX;
}
//设置矩形的边框坐标
setInnerBorder(left, top, right, bottom);
break;
//手指从屏幕上抬起或者事件被其他组件拦截掉该事件而造成的本次事件取消
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (touchFlag == TOUCH_SELECT) {
//手指抬起的时候,如果是选择标识,则换成移动标识
touchFlag = TOUCH_MOVE;
} else if (touchFlag == TOUCH_MOVE) {
//手指抬起的时候,如果是移动标识,则将最终坐标写进辅助变量中
tmpLeft = left;
tmpTop = top;
tmpRight = right;
tmpBottom = bottom;
}
//重新显示双击图片
if (isShouldShowTip() && isTimeToTip == false) {
isTimeToTip = true;
//重新绘制,调用onDraw()
invalidate();
}
//记录离开屏幕的时间
TouchUpTime = System.currentTimeMillis();
//计算本次ACTION_DOWN与ACTION_UP的时间间隔
timeInterval = TouchUpTime - TouchDownTime;
if (timeInterval < VALID_CLICK_INTERVAL && isInMoveSelection) {
//这是一次单击,将单击次数加1
innnerClickCount = innnerClickCount + 1;
if (innnerClickCount == 1) {
//记录第一次单击的时间
firstClickTime = System.currentTimeMillis();
} else if (innnerClickCount == 2) {
//记录第二次单击的时间
secondClickTime = System.currentTimeMillis();
//符合双击的条件则完成截图
if ((secondClickTime - firstClickTime) < VALID_DOUBLE_CLICK_INTERNAL) {
isTimeToTip = false;
mListener.onComplete(getCutImage());
dismiss();
}
innnerClickCount = 0;
}
} else if (timeInterval < VALID_CLICK_INTERVAL
&& !isInMoveSelection) {
//在非选中区域双击,取消本次截屏
outterClickCount = outterClickCount + 1;
if (outterClickCount == 1) {
firstClickTime = System.currentTimeMillis();
} else if (outterClickCount == 2) {
secondClickTime = System.currentTimeMillis();
if ((secondClickTime - firstClickTime) < VALID_DOUBLE_CLICK_INTERNAL) {
isTimeToTip = false;
dismiss();
}
outterClickCount = 0;
}
}
break;
default:
break;
}
return true;
}
private int getMin(int x, int y) {
return x > y ? y : x;
}
private int getMax(int x, int y) {
return x > y ? x : y;
}
/**
* 是否在左拉动选区
*
* @return
*/
private boolean isInLeftPullArea(int x, int y) {
if (((left - VALID_PULL_DISTANCE) <= x && x < (left + VALID_PULL_DISTANCE))
&& ((top + VALID_PULL_DISTANCE) < y && y < (bottom - VALID_PULL_DISTANCE))) {
return true;
}
return false;
}
/**
* 是否在右拉动选区
*
* @return
*/
private boolean isInRightPullArea(int x, int y) {
if (((right - VALID_PULL_DISTANCE) <= x && x < (right + VALID_PULL_DISTANCE))
&& ((top + VALID_PULL_DISTANCE) < y && y < (bottom - VALID_PULL_DISTANCE))) {
return true;
}
return false;
}
/**
* 是否在上拉动选区
*
* @return
*/
private boolean isInTopPullArea(int x, int y) {
if (((left + VALID_PULL_DISTANCE) <= x && x < (right - VALID_PULL_DISTANCE))
&& ((top - VALID_PULL_DISTANCE) < y && y < (top + VALID_PULL_DISTANCE))) {
return true;
}
return false;
}
/**
* 是否在下拉动选区
*
* @return
*/
private boolean isInBottomPullArea(int x, int y) {
if (((left + VALID_PULL_DISTANCE) <= x && x < (right - VALID_PULL_DISTANCE))
&& ((bottom - VALID_PULL_DISTANCE) < y && y < (bottom + VALID_PULL_DISTANCE))) {
return true;
}
return false;
}
/**
* 判断触碰的点有没有在移动选区内
*
* @param x
* @param y
* @return
*/
private boolean isInSeletion(int x, int y) {
if ((left != 0 || right != 0 || top != 0 || bottom != 0)
&& ((left + VALID_PULL_DISTANCE) <= x && x <= (right - VALID_PULL_DISTANCE))
&& ((top + VALID_PULL_DISTANCE) <= y && y <= (bottom - VALID_PULL_DISTANCE))) {
return true;
}
return false;
}
/**
* 判断是否应该出现提示
*
* @return
*/
private boolean isShouldShowTip() {
if ((right - left) > 100 && (bottom - top) > 100) {
return true;
}
return false;
}
/**
* 决定矩形选框的坐标
*
* @param xDistance
* @param yDistance
*/
private void decideCoordinate(int xDistance, int yDistance) {
innerWidth = right - left;
innerHeight = bottom - top;
// 决定左右坐标
if ((tmpLeft + xDistance) < 0) {
right = innerWidth;
left = 0;
} else if ((tmpRight + xDistance) > screenWidth) {
left = screenWidth - innerWidth;
right = screenWidth;
} else {
left = tmpLeft + xDistance;
right = tmpRight + xDistance;
}
// 决定上下坐标
if ((tmpTop + yDistance) < 0) {
bottom = innerHeight;
top = 0;
} else if ((tmpBottom + yDistance) > screeenHeight) {
top = screeenHeight - innerHeight;
bottom = screeenHeight;
} else {
top = tmpTop + yDistance;
bottom = tmpBottom + yDistance;
}
}
/**
* 设置矩形的边框坐标
*/
private void setInnerBorder(int left, int top, int right, int bottom) {
Log.i("com.example", "left:" + left + ",top:" + top + ",right:" + right
+ ",bottom:" + bottom);
topRect.set(0, 0, screenWidth, top);
rightRect.set(right, top, screenWidth, bottom);
bottomRect.set(0, bottom, screenWidth, screeenHeight);
leftRect.set(0, top, left, bottom);
this.invalidate();
}
/**
* 截取内层边框中的View.
*/
private Bitmap getCutImage() {
View view = mContext.getWindow().getDecorView();
Bitmap bmp = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
view.draw(canvas);
return imageCrop(bmp);
}
/**
* 裁剪图片
*/
private Bitmap imageCrop(Bitmap bitmap) {
int x = left<0?0:left;
int y = top + getStatusHeight();
int width = right- left;
int height = bottom - top;
if((width+x)>bitmap.getWidth()){
width = bitmap.getWidth()-x;
}
if((y+height)>bitmap.getHeight()){
height = bitmap.getHeight()-y;
}
return Bitmap.createBitmap(bitmap,x,y,width,height , null, false);
}
/**
* 返回状态栏的高度
*
* @return
*/
private int getStatusHeight() {
Rect frame = new Rect();
mContext.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
return frame.top;
}
/**
* 注销该view
*/
private void dismiss() {
isHide = true;
mGrayPaint.setARGB(0, 0, 0, 0);
setOnTouchListener(null);
mContext.setAllowFullScreen(true);
invalidate();
}
/**
* 获取不同设备的宽高
* @param display
* @return
*/
@SuppressLint("NewApi")
private static Point getDisplaySize(final Display display) {
final Point point = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
display.getSize(point);
} else {
point.x = display.getWidth();
point.y = display.getHeight();
}
return point;
}
/**
* 自定义View中的onKeyDown方法,默认情况下不调用View.onKeyDown方法,
* 但是在BaseActivity中的onKeyDown方法中可以显式调用
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && isHide == false) {
Log.i("ScreenShotView", "---->第一次摁返回键");
mContext.setAllowDestroy(false);
dismiss();
} else if ((keyCode == KeyEvent.KEYCODE_BACK && isHide == true)) {
Log.i("ScreenShotView", "---->第二次摁返回键");
mContext.setAllowDestroy(true);
}
return false;
}
}
2、net.oschaina.app.common.UIHelper
/**
* 分享到‘新浪微博‘或‘腾讯微博‘的对话框
*
* @param context
* 当前Activity
* @param title
* 分享的标题
* @param url
* 分享的链接
*/
public static void showShareDialog(final Activity context,
final String title, final String url) {
final String spiltUrl;
Log.i("UIHelper", "---->showShareDialog--->url:"+url);
//判断分享的链接是否包含my
if(url.indexOf("my")>0){
spiltUrl = "http://m.oschina.net/" + url.substring(url.indexOf("blog"));
}else{
spiltUrl = "http://m.oschina.net/" + url.substring(22);
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setIcon(android.R.drawable.btn_star);
builder.setTitle(context.getString(R.string.share));
builder.setItems(R.array.app_share_items,
new DialogInterface.OnClickListener() {
AppConfig cfgHelper = AppConfig.getAppConfig(context);
AccessInfo access = cfgHelper.getAccessInfo();
public void onClick(DialogInterface arg0, int arg1) {
switch (arg1) {
case 0:// 新浪微博
// 分享的内容
final String shareMessage = title + " " + url;
// 初始化微博
if (SinaWeiboHelper.isWeiboNull()) {
SinaWeiboHelper.initWeibo();
}
// 判断之前是否登陆过
if (access != null) {
SinaWeiboHelper.progressDialog = new ProgressDialog(
context);
SinaWeiboHelper.progressDialog
.setProgressStyle(ProgressDialog.STYLE_SPINNER);
SinaWeiboHelper.progressDialog
.setMessage(context
.getString(R.string.sharing));
SinaWeiboHelper.progressDialog
.setCancelable(true);
SinaWeiboHelper.progressDialog.show();
new Thread() {
public void run() {
SinaWeiboHelper.setAccessToken(
access.getAccessToken(),
access.getAccessSecret(),
access.getExpiresIn());
SinaWeiboHelper.shareMessage(context,
shareMessage);
}
}.start();
} else {
SinaWeiboHelper
.authorize(context, shareMessage);
}
break;
case 1:// 腾讯微博
QQWeiboHelper.shareToQQ(context, title, url);
break;
case 2:// 微信朋友圈
WXFriendsHelper.shareToWXFriends(context, title, spiltUrl);
break;
case 3:// 截图分享
//这就是所谓的回调接口OnScreenShotListener
addScreenShot(context, new OnScreenShotListener() {
@SuppressLint("NewApi")
public void onComplete(Bitmap bm) {
Intent intent = new Intent(context,ScreenShotShare.class);
intent.putExtra("title", title);
intent.putExtra("url", url);
intent.putExtra("cut_image_tmp_path",ScreenShotView.TEMP_SHARE_FILE_NAME);
try {
//将图片保存到SD卡中
ImageUtils.saveImageToSD(context,ScreenShotView.TEMP_SHARE_FILE_NAME,bm, 100);
} catch (IOException e) {
e.printStackTrace();
}
context.startActivity(intent);
}
});
break;
case 4:// 更多
showShareMore(context, title, spiltUrl);
break;
}
}
});
builder.create().show();
}
/**
* 添加截屏功能
*/
@SuppressLint("NewApi")
public static void addScreenShot(Activity context,
OnScreenShotListener mScreenShotListener) {
BaseActivity cxt = null;
if (context instanceof BaseActivity) {
cxt = (BaseActivity) context;
cxt.setAllowFullScreen(false);
ScreenShotView screenShot = new ScreenShotView(cxt,
mScreenShotListener);
//设置布局的参数
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
//将自定义截屏的View添加到当前Activity中
context.getWindow().addContentView(screenShot, lp);
}
}
3、net.oschina.app.ui.BaseActivity—-所有Activity中的基类
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
//如果view非空,则就是自定义View---ScreenShotView
if (keyCode == KeyEvent.KEYCODE_BACK && view != null) {
Log.i("BaseActivity", "---->onKeyDown");
//显式调用自定义View中的onKeyDown方法
view.onKeyDown(keyCode, event);
if (!allowDestroy) {
Log.i("BaseActivity", "---->第一次只销毁截屏的view,但不销毁Activity");
return false;
}
}
//如果view为空,直接销毁当前Activity
return super.onKeyDown(keyCode, event);
}
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。