前两年的疫情爆发让线上教育特别火,很多公司选择线上办公,很多学校线上教学,不过后来受政策影响很大。这些机构学校依赖的线上教育软件功能强大又繁杂,功能不仅有基础的书写板书,还能添加图片,打字、播放视频、3D模型…作为安卓程序员要实现那些功能可能需要很多多的时间和精力。
废话不多说,那么本人这次分享一下这黑板的基本实现。包含的功能也不多,包含了手绘,输入文字,选择图层,删除,图片缩放,添加小黑板,缩放、拖拽、删除小黑板,混动整个黑板(大概一个屏幕的50倍高度),每个功能对应下面按钮介绍。如图
实现思路实是这样的,每个元素是用图层来实现的,每一个元素生成一张图片,这样才能拖动图片到另一张图片上,因为使用的是图片,所以这其中会有稍许频繁的图片操作,并不是很友好,元素内容用hashmap来管理,图片拖动是用ViewDragHelper来管理。
既然是黑板。那么自定义view流程还是要稍微熟悉的,这些不概述了。
- 创建图层
/**
* 创建图层并添加到BoardLayout
*/
private void createImageView() {
creatBitmap();
try {
Bitmap newbm =null;
if(board.action==BoardAction.ACTION_DRAW){
cacheCanvas.drawPath(path,paint);
cacheCanvas.save();
cacheCanvas.restore(); // 存储
//裁剪出需要保存的bitmap坐标和高宽需要加上画笔的宽度。不然会裁切到线条.*2表示左右两边都需要加线条宽度
newbm = Bitmap.createBitmap(cacheBitmap, Math.max((int) ((int)minX-paint.getStrokeWidth()),0),Math.max((int) ((int)minY-paint.getStrokeWidth()),0),
(int) ((int)(maxX-Math.max(minX,0))+paint.getStrokeWidth()*2),(int) ((int)(maxY-Math.max(minY,0))+paint.getStrokeWidth()*2));
}else if(board.action==BoardAction.ACTION_DRAW_TEXT){
cacheCanvas.drawText(drawText,preX,preY,paint);
cacheCanvas.save();
cacheCanvas.restore(); // 存储
//裁剪出需要保存的bitmap坐标和高宽需要加上画笔的宽度。不然会裁切到线条.*2表示左右两边都需要加线条宽度
newbm = Bitmap.createBitmap(cacheBitmap, (int)minX,(int)minY,
(int) ((int) (maxX-minX)+paint.getStrokeWidth()*2),(int)(maxY-minY));
}
//向当前view中添加图片
if(minX==0&&minY==0&&maxX==0&&maxY==0){
}else{
DragScaleView imageView=new DragScaleView(getContext());
imageView.setImageBitmap(newbm);
int l=Math.max((int) ((int)minX-paint.getStrokeWidth()),0);
int t=Math.max((int) ((int)minY-paint.getStrokeWidth()),0);
ViewInfo info=board.setViewInfo(l,t,l+newbm.getWidth(),t+newbm.getHeight(),newbm.getWidth(),newbm.getHeight());
if(board.action==BoardAction.ACTION_DRAW_TEXT){//输入的是文字需要记录文字内容和按下的xy坐标
info.setContent(drawText);
info.setPreX(preX);
info.setPreY(preY);
info.setType(ViewType.VIEW_TEXT);
imageView.setOnClickListener(this);
}else if(board.action==BoardAction.ACTION_DRAW){
info.setType(ViewType.VIEW_DRAW);
}
board.addDragChildView(imageView,info);
addView(imageView,smallBoardNum<=0?-1:getChildCount()-smallBoardNum);//如果没有小黑板就添加到小黑板的下面
if(board.action==BoardAction.ACTION_DRAW_TEXT){//设置文字为选中状态
board.chooseView(imageView);
}
}
setMmPoint(0,0,0,0);
cacheBitmap=null;
}catch (Exception e){
Log.e("黑板创建图层错误",e.toString());
}
}
/**
* 创建bitmap以便裁切
*/
private void creatBitmap() {
int view_height=board.boardHight+marginTop;//加上偏移量
// 创建一个与该View相同大小的缓存区
cacheBitmap = Bitmap.createBitmap(board.boardWidth, view_height,Bitmap.Config.ALPHA_8);
cacheCanvas =new Canvas(cacheBitmap);
}
-
按钮1
选择功能主要是为了框选黑板上的元素内容,以便拖拽删除放大如图
ViewDragHelper捕获到某个元素后设置选中框,拖动时再将选中的元素一起移动,大致代码如下
dragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
/**
* 是否捕获childView:
* 如果viewList包含child,那么捕获childView
* 如果不包含child,就不捕获childView
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(board.action== BoardAction.ACTION_DRAW||board.action==BoardAction.ACTION_DRAW_TEXT||board.action==BoardAction.ACTION_MOVE){//绘制模式和文字模式
return false;
}else{
return board.containsView(child);
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
for (Map.Entry entry :board.viewsMap.entrySet()) {
ViewInfo info=entry.getValue();
if(info.isChoose()){
View view = entry.getKey();
info.setLeft(info.getLeft()+dx);
info.setTop(info.getTop()+dy);
info.setRight(info.getRight()+dx);
info.setBottom(info.getBottom()+dy);
if(info.getType()== ViewType.VIEW_TEXT){//如果View是文字内容需要重置PreX和PreY
info.setPreX(info.getPreX()+dx);
info.setPreY(info.getPreY()+dy);
}
board.viewsMap.put(view,info);
if(view!=changedView){//非当前按下的view也需要移动
view.layout(info.getLeft(),info.getTop(), info.getRight(), info.getBottom());
}
}
}
}
/**
* 当捕获到child后的处理:
* 获取child的监听
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
jumpDraw=true;
//遍历map,如果按下的view在选中状态的时候。不需要重新设置选中的view。该操作是在拖动view了
for (Map.Entry entry :board.viewsMap.entrySet()) {
ViewInfo info=entry.getValue();
if(!(entry.getKey()instanceof DragBoardView)){
DragScaleView view= (DragScaleView) entry.getKey();
view.setCanTouch(false);//遍历禁止View可以操作
if(!info.isChoose()&&capturedChild==entry.getKey()){
//得到一个被选中的View,该View可以实现缩放
board.setScaView(info.getType()==ViewType.VIEW_IMAGE?(DragScaleView) capturedChild:null);
board.setScaViewInfo(info);
board.chooseView(capturedChild);
}
}
}
onDragDrop(true);
}
/**
* 当释放child后的处理:
* 取消监听,不再处理
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
jumpDraw=false;
onDragDrop(false);
}
/**
* 当前view的left
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
/**
* 到上边界的距离
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
});
选中状态
/**
* 拖动选择框选中View并修改选中状态
* @param minX
* @param minY
* @param maxX
* @param maxY
*/
public void chooseView(float minX, float minY, float maxX, float maxY) {
int chooseViewNum=0;
for (Map.Entry entry :viewsMap.entrySet()) {
if( !(entry.getKey()instanceof DragBoardView)){
ViewInfo info=entry.getValue();
float zx = Math.abs(minX+maxX-info.getLeft()-info.getRight()); //两个矩形重心在x轴上的距离的两倍
float x = (Math.abs(minX-maxX)+Math.abs(info.getLeft()-info.getRight())); //两矩形在x方向的边长的和
float zy = Math.abs(minY+maxY-info.getTop()-info.getBottom()); //重心在y轴上距离的两倍
float y = (Math.abs(minY-maxY)+Math.abs(info.getTop()-info.getBottom())); //y方向边长的和
DragScaleView view = (DragScaleView) entry.getKey();
view.setCanTouch(false);
if(action==BoardAction.ACTION_CHOOSE){
view.setClickable(false);//禁止点击事件
}else if(action==BoardAction.ACTION_DRAW_TEXT&&info.getType()==ViewType.VIEW_TEXT){
view.setClickable(true);
}
if(zx <= x && zy <= y){//view在选择框中
view.setBackgroundResource(view_BG);
info.setChoose(true);
chooseViewNum++;
if (chooseViewNum==1){//选中的view只能是一个的时候才能放大缩小,否则就清空
scaView=view;
}
}else{
view.setBackgroundResource(0);
info.setChoose(false);
}
viewsMap.put(view,info);
}
}
if(chooseViewNum==1){
setScaViewInfo(viewsMap.get(scaView));
}else{
scaView=null;
}
}
-
按钮2
书写功能类似老师在黑板上板书。
- 绘制的基本功能肯定是在onTouchEvent事件中处理逻辑
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
// // 获取触摸事件的发生位置
float x = event.getX();
float y = event.getY();
if(jumpDraw){
return true;
}else{
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//得到第一个点的xy坐标
setMmPoint(x,y,x,y);
preX = x;
preY = y;
if(board.action==BoardAction.ACTION_CHOOSE){
board.chooseRect.setRectVal((int)preX,(int)preY,(int)preX,(int)preY);
}else if(board.action==BoardAction.ACTION_MOVE){
}else{
if(drawType== BoardDrawType.DRAW_PATH){
path.moveTo(x, y); // 将绘图的起始点移到(x,y)坐标点的位置
}else if(drawType== BoardDrawType.DRAW_TEXT){//输入文字模式的时候点击黑板时回调展示输入的dialog
textChooseView=null;
onShowInputTextDialog("");
}else{
}
}
break;
case MotionEvent.ACTION_MOVE:
if(board.action==BoardAction.ACTION_CHOOSE){
board.chooseRect.setRectVal((int) x, (int) y);
}else if(board.action==BoardAction.ACTION_MOVE) {
}else{
if(drawType== BoardDrawType.DRAW_PATH){
float dx = Math.abs(x -preX);
float dy = Math.abs(y -preY);
if (dx >=3 || dy >=3) {// 两点之间的距离大于等于3时,生成贝塞尔绘制曲线
path.quadTo(preX, preY, (x +preX) /2, (y +preY) /2);
preX = x;
preY = y;
}
}else{
}
}
if(x
minX=x;
}
if(x>maxX){
maxX=x;
}
if(y
minY=y;
}
if(y>maxY){
maxY=y;
}
break;
case MotionEvent.ACTION_UP://拖动图片的时候抬起手势也会执行该方法。(jumpDraw先设置了false)
if(board.action==BoardAction.ACTION_CHOOSE){
if(minX==0&&minY==0&&maxX==0&&maxY==0){
}else{
board.chooseView(minX,minY,maxX,maxY);
board.chooseRect.rect=new Rect();
setMmPoint(0,0,0,0);
}
}else if(board.action==BoardAction.ACTION_MOVE) {
}else{
if(board.action==BoardAction.ACTION_DRAW){
createImageView();
path.reset();//清空所有path至原始状态。
}
}
break;
}
}
invalidate();//view刷新
return true;// 返回true表明处理方法已经处理该事件
}
- onDraw绘制
@SuppressLint("DrawAllocation")
@Override
public void onDraw(Canvas canvas) {
if(board.action==BoardAction.ACTION_CHOOSE){
board.drawRec(canvas);
}else if(board.action==BoardAction.ACTION_MOVE) {
board.drawRec(canvas);
}else{
if(drawType== BoardDrawType.DRAW_PATH){
board.drawPath(canvas);
}else if(drawType== BoardDrawType.DRAW_TEXT){
if(drawText.isEmpty()){
return;
}
canvas.drawText(drawText,preX,preY,paint);
Rect rect =new Rect();
paint.getTextBounds(drawText, 0, drawText.length(), rect);
int w = rect.width();
int h = rect.height();
Paint.FontMetrics fontMetrics=paint.getFontMetrics();
minX=preX;
maxX=minX+w;
maxY=preY+fontMetrics.descent;
minY=maxY+fontMetrics.top;
createImageView();
drawText="";
}
}
}
-
按钮3
文字功能是直接打字在黑板上,获取到点击事件的坐标点后。弹出输入框输
- 输入框代码
public class InputTextMsgDialogextends AppCompatDialog {
private ContextmContext;
private InputMethodManagerimm;
public EditTextmessageTextView;
private TextViewconfirmBtn;
private RelativeLayoutrlDlg;
private int mLastDiff =0;
private TextViewtvNumber;
private TextViewtvSend;
private int maxNumber =200;
public interface OnTextSendListener {
void onTextChange(String msg);
void onTextSend(String msg);
}
private OnTextSendListenermOnTextSendListener;
public InputTextMsgDialog(@NonNull Context context, int theme) {
super(context, theme);
this.mContext = context;
this.getWindow().setWindowAnimations(R.style.class_main_menu_animstyle);
init();
setLayout();
}
/**
* 最大输入字数 默认200
*/
@SuppressLint("SetTextI18n")
public void setMaxNumber(int maxNumber) {
this.maxNumber = maxNumber;
tvNumber.setText("0/" + maxNumber);
}
/**
* 设置输入提示文字
*/
public void setHint(String text) {
messageTextView.setHint(text);
}
/**
* 设置按钮的文字 默认为:发送
*/
public void setBtnText(String text) {
confirmBtn.setText(text);
}
private void init() {
setContentView(R.layout.class_edit_pop);
messageTextView = (EditText) findViewById(R.id.edit_content);
tvSend = (TextView) findViewById(R.id.chat_send_btn);
final LinearLayout rldlgview = (LinearLayout) findViewById(R.id.rl_inputdlg_view);
TextView other=(TextView) findViewById(R.id.other_view);
imm = (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
this.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
hideKeyboard(messageTextView.getWindowToken(),mContext);
}
});
other.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
messageTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String msg =messageTextView.getText().toString().trim();
if (msg.length() >maxNumber) {
Toast.makeText(mContext, "超过最大字数限制", Toast.LENGTH_LONG).show();
return;
}
if(mOnTextSendListener!=null){
mOnTextSendListener.onTextChange(msg);
}
}
});
tvSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String msg =messageTextView.getText().toString().trim();
if (msg.length() >maxNumber) {
Toast.makeText(mContext, "超过最大字数限制", Toast.LENGTH_LONG).show();
return;
}
if (!TextUtils.isEmpty(msg)) {
if(mOnTextSendListener!=null){
mOnTextSendListener.onTextSend(msg);
}
// imm.showSoftInput(messageTextView, InputMethodManager.SHOW_FORCED);
// imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
messageTextView.setText("");
dismiss();
}else {
// Toast.makeText(mContext, "请输入文字", Toast.LENGTH_LONG).show();
// return;
dismiss();
}
messageTextView.setText(null);
}
});
messageTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
switch (actionId) {
case KeyEvent.KEYCODE_ENDCALL:
case KeyEvent.KEYCODE_ENTER:
if (messageTextView.getText().length() >maxNumber) {
Toast.makeText(mContext, "超过最大字数限制", Toast.LENGTH_LONG).show();
return true;
}
if (messageTextView.getText().length() >0) {
// imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
// dismiss();
if(mOnTextSendListener!=null){
mOnTextSendListener.onTextSend(messageTextView.getText().toString());
}
}else {
// Toast.makeText(mContext, "请输入文字", Toast.LENGTH_LONG).show();
}
dismiss();
messageTextView.setText(null);
return true;
case KeyEvent.KEYCODE_BACK:
dismiss();
return false;
default:
return false;
}
}
});
messageTextView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
Log.d("My test", "onKey " + keyEvent.getCharacters());
return false;
}
});
rlDlg = findViewById(R.id.rl_outside_view);
rlDlg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() != R.id.rl_inputdlg_view)
dismiss();
}
});
rldlgview.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
Rect r =new Rect();
//获取当前界面可视部分
getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
//获取屏幕的高度
int screenHeight = getWindow().getDecorView().getRootView().getHeight();
//此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数
int heightDifference = screenHeight - r.bottom;
if (heightDifference <=0 &&mLastDiff >0) {
dismiss();
}
mLastDiff = heightDifference;
}
});
rldlgview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
dismiss();
}
});
setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialogInterface, int keyCode, KeyEvent keyEvent) {
if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.getRepeatCount() ==0)
dismiss();
return false;
}
});
}
private void setLayout() {
getWindow().setGravity(Gravity.BOTTOM);
WindowManager m = getWindow().getWindowManager();
Display d = m.getDefaultDisplay();
WindowManager.LayoutParams p = getWindow().getAttributes();
p.width = WindowManager.LayoutParams.MATCH_PARENT;
p.height = WindowManager.LayoutParams.MATCH_PARENT;
getWindow().setAttributes(p);
setCancelable(true);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
public void setmOnTextSendListener(OnTextSendListener onTextSendListener) {
this.mOnTextSendListener = onTextSendListener;
}
@Override
public void dismiss() {
imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
super.dismiss();
//dismiss之前重置mLastDiff值避免下次无法打开
mLastDiff =0;
}
@Override
public void show() {
super.show();
openKeyboard(messageTextView,mContext);
//延时加载dialog避免输入法无法弹出
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
openKeyboard(messageTextView,mContext);
}
},200);
}
public void setContentText(String s){
messageTextView.setText(s);
messageTextView.setSelection(s.length());//将光标移至文字末尾
}
/**
* 获取InputMethodManager,隐藏软键盘
*
* @param token
*/
private void hideKeyboard(IBinder token, Context context) {
if (token !=null) {
InputMethodManager im = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
/**
* 获取InputMethodManager,打开软键盘
*
*/
private void openKeyboard(EditText et, Context context) {
et.setFocusable(true);
et.setFocusableInTouchMode(true);
et.requestFocus();
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(et, InputMethodManager.RESULT_UNCHANGED_SHOWN);
}
}
- 布局文件class_edit_pop
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:gravity="bottom"
android:id="@+id/rl_outside_view"
android:layout_height="match_parent">
android:id="@+id/other_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/rl_inputdlg_view"/>
android:id="@+id/rl_inputdlg_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
android:id="@+id/edit_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="15dp"
android:background="@drawable/common_shape_line_100"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:textColor="@color/black"
android:maxLines="3"
android:maxLength="200"
android:layout_marginRight="15dp"
android:hint="聊天输入"
android:padding="10dp" />
android:id="@+id/chat_send_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/teal_200"
android:paddingTop="7dp"
android:paddingBottom="7dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:layout_marginRight="16dp"
android:text="发送"/>
</RelativeLayout>
-
按钮4
删除黑板上的元素
/**
* 删除黑板中选中的view
*/
public void removeChooseView(ViewGroup group) {
for (Iterator> it =viewsMap.entrySet().iterator(); it.hasNext();){
Map.Entry entry = it.next();
//删除key值为Two的数据
if (entry.getValue().isChoose()) {
group.removeView(entry.getKey());
it.remove();
}
}
}
-
按钮5
插入图片功能每选择一张图片都放到黑板左上角,并且叠加到最上层,支持缩放
-
按钮6
拖动黑板需要自己代码设置黑板大小,才能滚动黑板到底部。demo中设置了50x屏幕宽度
boardParams =boardView.layoutParams as RelativeLayout.LayoutParams//取控件黑板的布局参数
boardParams!!.width = Util.getScreenInfo(this).widthPixels
boardParams!!.height=Util.getScreenInfo(this).heightPixels*50//50倍黑板
boardView.layoutParams =boardParams!!
-
按钮7
添加小黑板功能属于黑板中的另一个黑板,支持书写,放大缩小和拖拽
- 小黑板布局contr_view
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/title_R"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/cardview_dark_background"
android:padding="10dp">
android:id="@+id/contr_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
android:layout_centerInParent="true"/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true">
android:id="@+id/contr_small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="20sp"
android:padding="10dp"
android:text="-"/>
android:id="@+id/contr_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="20sp"
android:layout_marginLeft="10dp"
android:padding="10dp"
android:text="X"/>
android:id="@+id/contr_scrollView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:fillViewport="true">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<!--黑板-->
android:id="@+id/contr_view"
android:background="@color/design_default_color_secondary_variant"
android:layout_width="match_parent"
android:layout_height="match_parent">
</merge>
- 其中DragBoardView处理拖拽放大缩小
public class DragBoardViewextends LinearLayout/*implements View.OnTouchListener */{
private final int touchDistance =40; //触摸边界的有效距离
private final int TOP =0x15;
private final int LEFT =0x16;
private final int BOTTOM =0x17;
private final int RIGHT =0x18;
private final int LEFT_TOP =0x11;
private final int RIGHT_TOP =0x12;
private final int LEFT_BOTTOM =0x13;
private final int RIGHT_BOTTOM =0x14;
private final int TITLE =0x19;
protected int lastdistX;
protected int lastlastY;
private int dragDirection;
protected int lastX;
protected int lastY;
private int oriLeft;
private int oriRight;
private int oriTop;
private int oriBottom;
private int offset =0; //可超出其父控件的偏移量
private int ACTION_DOWN_VAL=0;//按下的时候的值。记录下来。
private TextViewtitle;
private TextViewsmall;
private TextViewclose;
private RelativeLayouttitle_R;
private MScrollViewscrollView;
private NomalBoardLayoutboard;
private SmallBoardStatusListenerstatusListener;
private boolean animting;//是否正在动画中
public void setAnimting(boolean animting) {
this.animting = animting;
}
public void setStatusListener(SmallBoardStatusListener statusListener) {
this.statusListener = statusListener;
}
private int mHeight=0;
private int mWidth=0;
public DragBoardView(Context context) {
this(context,null);
}
public DragBoardView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public DragBoardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
View parentView = LayoutInflater.from(getContext()).inflate(R.layout.contr_view, this, true);
this.setOrientation(LinearLayout.VERTICAL);
title=parentView.findViewById(R.id.contr_title);
small=parentView.findViewById(R.id.contr_small);
close=parentView.findViewById(R.id.contr_close);
scrollView=parentView.findViewById(R.id.contr_scrollView);
board=parentView.findViewById(R.id.contr_view);
title_R=parentView.findViewById(R.id.title_R);
scrollView.setScroll(false);
board.setAction(BoardAction.ACTION_DRAW);
close.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
statusListener.boardClose(DragBoardView.this);
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
dragDirection = getDirection(this, (int) ev.getX(),
(int) ev.getY());
if(ev.getAction()==MotionEvent.ACTION_DOWN){
ACTION_DOWN_VAL=dragDirection;//记录第一次按下的时候的值是标题栏TITLE的话,在ACTION_MOVE的时候才能拖动
}
if(dragDirection!=-1&&dragDirection!=TITLE&&ACTION_DOWN_VAL!=TITLE){//避免拦截了关闭等按钮
return true;
}
return super.onInterceptTouchEvent(ev);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(animting){
return true;
}
if(dragDirection==TITLE&&ACTION_DOWN_VAL==TITLE){
statusListener.boardMove(this,event);
}else if(ACTION_DOWN_VAL!=-1/*&&ACTION_DOWN_VAL==TITLE*/){//第一次按下的时候不能在黑板中才可以执行
int action = event.getAction()& MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_DOWN) {
oriLeft = getLeft();
oriRight = getRight();
oriTop = getTop();
oriBottom = getBottom();
lastY = (int) event.getRawY();
lastX = (int) event.getRawX();
}
// 处理拖动事件
delDrag(this, event, action);
}
return true;
}
/**
* 处理拖动事件
*
* @param v
* @param event
* @param action
*/
protected void delDrag(View v, MotionEvent event, int action) {
switch (action) {
case MotionEvent.ACTION_MOVE:
int dx = (int) event.getRawX() -lastX;
int dy = (int) event.getRawY() -lastY;
switch (dragDirection) {
case TITLE://标题栏
return;
case LEFT:// 左边缘
left(v, dx);
break;
case RIGHT:// 右边缘
right(v, dx);
break;
case BOTTOM:// 下边缘
bottom(v, dy);
break;
case TOP:// 上边缘
top(v, dy);
break;
case LEFT_BOTTOM:// 左下
left(v, dx);
bottom(v, dy);
break;
case LEFT_TOP:// 左上
left(v, dx);
top(v, dy);
break;
case RIGHT_BOTTOM:// 右下
right(v, dx);
bottom(v, dy);
break;
case RIGHT_TOP:// 右上
right(v, dx);
top(v, dy);
break;
}
if (dragDirection != -1) {
statusListener.boardSizeChange(this,oriLeft, oriTop, oriRight, oriBottom);
}
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
ACTION_DOWN_VAL=0;
dragDirection =0;
lastdistX=0;
lastlastY=0;
break;
}
}
/**
* 触摸点为左边缘
*
* @param v
* @param dx
*/
private void left(View v, int dx) {
oriLeft += dx;
}
/**
* 触摸点为右边缘
*
* @param v
* @param dx
*/
private void right(View v, int dx) {
oriRight += dx;
}
/**
* 触摸点为下边缘
*
* @param v
* @param dy
*/
private void bottom(View v, int dy) {
oriBottom += dy;
}
/**
* 触摸点为上边缘
*
* @param v
* @param dy
*/
private void top(View v, int dy) {
oriTop += dy;
}
/**
* 获取触摸点flag
*
* @param v
* @param x
* @param y
* @return
*/
protected int getDirection(View v, int x, int y) {
int left = v.getLeft();
int right = v.getRight();
int bottom = v.getBottom();
int top = v.getTop();
if (x
return LEFT_TOP;
}
if(x>touchDistance&&xtouchDistance&&y
return TITLE;
}
if (y
return RIGHT_TOP;
}
if (x
return LEFT_BOTTOM;
}
if (right - left - x
return RIGHT_BOTTOM;
}
if (x
return LEFT;
}
if (y
return TOP;
}
if (right - left - x
return RIGHT;
}
if (bottom - top - y
return BOTTOM;
}
return -1;
}
/**
* 黑板移动监听
*/
public interface SmallBoardStatusListener{
void boardMove(View v, MotionEvent event);
void boardClose(View v);
void boardSizeChange(View v,int l, int t, int r, int b);
}
}
- boardMove回调中添加移动动画
@Override
public void boardMove(View v, MotionEvent event) {
setAction(BoardAction.ACTION_MOVE);
float x =event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
board.chooseRect.setRectVal(board.viewsMap.get(v).getLeft(), board.viewsMap.get(v).getTop(),board.viewsMap.get(v).getRight(),board.viewsMap.get(v).getBottom());
left=board.viewsMap.get(v).getLeft();
top=board.viewsMap.get(v).getTop();
v.bringToFront();
break;
case MotionEvent.ACTION_MOVE:
int dx= (int) (x-board.chooseRect.preX);
int dy= (int) (y-board.chooseRect.preY);
board.chooseRect.setRectVal(board.chooseRect.rect.left+dx,board.chooseRect.rect.top+dy,
board.chooseRect.rect.right+dx,board.chooseRect.rect.bottom+dy);
invalidate();//view刷新
break;
case MotionEvent.ACTION_UP:
ViewInfo info=board.viewsMap.get(v);
info.setLeft(board.chooseRect.rect.left);
info.setTop(board.chooseRect.rect.top);
info.setRight(board.chooseRect.rect.right);
info.setBottom(board.chooseRect.rect.bottom);
TranslateAnimation animation =new TranslateAnimation(0, info.getLeft()-left, 0, info.getTop()-top);//传入移动的距离而不是绝对值的坐标点
animation.setDuration(300);
v.setAnimation(animation);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
DragBoardView boardView=(DragBoardView)v;
boardView.setAnimting(true);
}
@Override
public void onAnimationEnd(Animation animation) {
DragBoardView boardView=(DragBoardView)v;
boardView.setAnimting(false);
animation.cancel();
v.clearAnimation();
board.viewsMap.put(v,info);
v.layout(info.getLeft(),info.getTop(),info.getRight(),info.getBottom());
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
break;
}
board.chooseRect.preX=x;
board.chooseRect.preY=y;
}
以上为部分代码
demo现目前只有这些功能,并且很多功能是有一些bug的,只是提供一种功能的思路。如果对你有帮助麻烦点个赞Star