package com.weilu.customview.widget; import android.content.ClipDescription; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.DragEvent; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import com.weilu.customview.bean.DraggableInfo; import com.weilu.customview.utils.Tools; import java.util.ArrayList; import java.util.List; import static com.weilu.customview.utils.Tools.ONE_BY_ONE_PIC; import static com.weilu.customview.utils.Tools.ONE_BY_ONE_TEXT; import static com.weilu.customview.utils.Tools.ONE_BY_TWO_PIC; import static com.weilu.customview.utils.Tools.THREE_BY_THREE_PIC; /** * @Description: 自定义遥控器 * @Author: weilu * @Time: 2018/2/23 09:59. */ public class RemoteControlView extends FrameLayout implements View.OnDragListener{ private final static String TAG = "RemoteControlView"; /** * 绘制手机画笔 */ private Paint mPhonePaint; /** * 手机返回按键path */ private Path mBackPath; /** * 手机宽度 */ private int mPhoneWidth; /** * 手机内容区域高 */ private int mPhoneContentHeight; /** * 手机内容区域宽 */ private int mPhoneContentWidth; /** * 手机右上角x轴点 */ private int startX; /** * 存放按钮位置 */ private List<Rect> mRectList = new ArrayList<>(); /** * 内部拖拽的View */ private View dragView; /** * 内部拖拽View的位置 */ private Rect dragRect; /** * 内部拖拽是否出界 */ private boolean isOut; /** * 提示投影的位置 */ private Rect shadowRect; /** * 拖拽按钮的信息 */ private DraggableInfo info; /** * 文字Rect */ Rect mTextRect = new Rect(); /** * 投影图片的Bitmap */ private Bitmap shadowBitmap; /** * 临时Rect */ private Rect mRect = new Rect(); private FrameLayout frameLayout; private TextView mTextView; private final static int WIDTH_COUNT = 4; private final static int HEIGHT_COUNT = 7; private final static String BORDER_COLOR = "#70FFFFFF"; private final static String SOLID_COLOR = "#30FFFFFF"; private final static String DASHED_COLOR = "#20FFFFFF"; private final static String CONTENT_COLOR = "#0E000000"; private DashPathEffect mDashPathEffect = new DashPathEffect(new float[] {10, 10}, 0); public RemoteControlView(Context context) { this(context, null); } public RemoteControlView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RemoteControlView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { setWillNotDraw(false); mPhonePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBackPath = new Path(); // 不使用硬件加速,否则虚线显示不出 setLayerType(LAYER_TYPE_SOFTWARE, null); // 拖拽有效区域 frameLayout = new FrameLayout(context); frameLayout.setBackgroundColor(Color.parseColor(CONTENT_COLOR)); frameLayout.setOnDragListener(this); addView(frameLayout); // 提示文字 mTextView = new TextView(context); mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); mTextView.setTextColor(Color.WHITE); mTextView.setText("长按并拖拽下方按钮到这里"); LayoutParams fl = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); fl.gravity = Gravity.CENTER; mTextView.setLayoutParams(fl); mTextView.measure(0, 0); addView(mTextView); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec)); // 手机高度为View高度减去上下间隔24dp int phoneHeight = getMeasuredHeight() - dp2px(24); // 手机内容区域高 :手机高度 - 手机头尾(48dp)- 手机屏幕间距(5dp) * 2) mPhoneContentHeight = phoneHeight - dp2px(58); // 手机内容区域宽 :手机内容区域高/ 7 * 4(手机内容区域为4:7) mPhoneContentWidth = mPhoneContentHeight / HEIGHT_COUNT * WIDTH_COUNT; // 手机宽度为手机内容区域宽 + 手机屏幕间距 * 2 mPhoneWidth = mPhoneContentWidth + dp2px(10); // 绘制起始点 startX = (getMeasuredWidth() - mPhoneWidth) / 2; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); frameLayout.layout(startX, dp2px(36), getMeasuredWidth() - startX, getMeasuredHeight() - dp2px(36)); if(frameLayout.getChildCount() > 0){ for (int i = 0; i < frameLayout.getChildCount(); i++){ Rect rect = mRectList.get(i); frameLayout.getChildAt(i).layout(rect.left, rect.top, rect.right, rect.bottom); } } } private int measure(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.AT_MOST: // 子容器可以是声明大小内的任意大小 result = specSize; break; case MeasureSpec.EXACTLY: // 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间 result = specSize; break; case MeasureSpec.UNSPECIFIED: // 父容器对于子容器没有任何限制,子容器想要多大就多大. 所以完全取决于子view的大小 result = dp2px(300); break; default: break; } return result; } private RectF mRectF = new RectF(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPhonePaint.setColor(Color.parseColor(BORDER_COLOR)); mPhonePaint.setStyle(Paint.Style.STROKE); mPhonePaint.setStrokeWidth(2); int i = dp2px(12); // 绘制手机外壳 mRectF.left = startX; mRectF.right = getMeasuredWidth() - startX; mRectF.top = i; mRectF.bottom = getMeasuredHeight() - i; canvas.drawRoundRect(mRectF, i, i, mPhonePaint); // 绘制手机上下两条线 canvas.drawLine(startX, i * 3, getMeasuredWidth() - startX, i * 3, mPhonePaint); canvas.drawLine(startX, getMeasuredHeight() - i * 3, getMeasuredWidth() - startX, getMeasuredHeight() - i * 3, mPhonePaint); // 绘制手机上方听筒、摄像头 mRectF.left = getMeasuredWidth() / 2 - dp2px(25); mRectF.right = getMeasuredWidth() / 2 + dp2px(25); mRectF.top = dp2px(22); mRectF.bottom = dp2px(26); canvas.drawRoundRect(mRectF, dp2px(2), dp2px(2), mPhonePaint); canvas.drawCircle(getMeasuredWidth() / 2 - dp2px(40), i * 2, i / 3, mPhonePaint); canvas.drawCircle(getMeasuredWidth() / 2 + dp2px(40), i * 2, i / 3, mPhonePaint); // 绘制手机下方按键 canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() - i * 2, i / 2, mPhonePaint); canvas.drawRect(startX + mPhoneWidth / 5, getMeasuredHeight() - dp2px(29), startX + mPhoneWidth / 5 + dp2px(10), getMeasuredHeight() - dp2px(19), mPhonePaint); mBackPath.moveTo(getMeasuredWidth() - startX - mPhoneWidth / 5, getMeasuredHeight() - dp2px(30)); mBackPath.lineTo(getMeasuredWidth() - startX - mPhoneWidth / 5 - dp2px(10), getMeasuredHeight() - dp2px(24)); mBackPath.lineTo(getMeasuredWidth() - startX - mPhoneWidth / 5, getMeasuredHeight() - dp2px(18)); mBackPath.close(); canvas.drawPath(mBackPath, mPhonePaint); // 绘制网格(4 * 7的田字格)田字格外框为实线,内侧为虚线 // 手机屏幕间距5pd int j = dp2px(5); // 格子的宽高 int size = mPhoneContentHeight / HEIGHT_COUNT; // 横线 for (int z = 0; z <= HEIGHT_COUNT; z++){ mPhonePaint.setPathEffect(null); mPhonePaint.setColor(Color.parseColor(SOLID_COLOR)); mPhonePaint.setStrokeWidth(1); // 实线 canvas.drawLine(startX + j, dp2px(41) + z * size, getMeasuredWidth() - startX - j, dp2px(41) + z * size, mPhonePaint); // 虚线 if (z != HEIGHT_COUNT){ mPhonePaint.setPathEffect(mDashPathEffect); mPhonePaint.setColor(Color.parseColor(DASHED_COLOR)); canvas.drawLine(startX + j, dp2px(41) + z * size + size / 2, getMeasuredWidth() - startX - j, dp2px(41) + z * size + size / 2, mPhonePaint); } } // 竖线 for (int z = 0; z <= WIDTH_COUNT; z++){ mPhonePaint.setPathEffect(null); mPhonePaint.setColor(Color.parseColor(SOLID_COLOR)); mPhonePaint.setStrokeWidth(1); // 实线 canvas.drawLine(startX + j + z * size, dp2px(41), startX + j + z * size, getMeasuredHeight() - dp2px(41), mPhonePaint); // 虚线 if (z != WIDTH_COUNT){ mPhonePaint.setPathEffect(mDashPathEffect); mPhonePaint.setColor(Color.parseColor(DASHED_COLOR)); canvas.drawLine(startX + j + z * size + size / 2, dp2px(41), startX + j + z * size + size / 2, getMeasuredHeight() - dp2px(41), mPhonePaint); } } if (shadowRect != null){ int type = info.getType(); mPhonePaint.setStyle(Paint.Style.FILL); mPhonePaint.setColor(Color.WHITE); shadowRect.left = shadowRect.left + startX; shadowRect.right = shadowRect.right + startX; shadowRect.top = shadowRect.top + dp2px(36); shadowRect.bottom = shadowRect.bottom + dp2px(36); if (type == ONE_BY_ONE_TEXT){ int width = shadowRect.right - shadowRect.left; String text = info.getText(); mPhonePaint.setTextSize(width / 4); mPhonePaint.getTextBounds(text, 0, text.length(), mTextRect); int textHeight = mTextRect.bottom - mTextRect.top; int textWidth = mTextRect.right - mTextRect.left; canvas.drawText(text, shadowRect.left + width / 2 - textWidth / 2, shadowRect.top + width / 2 + textHeight / 2, mPhonePaint); }else { if (type == ONE_BY_ONE_PIC){ // 1 * 1 方格 int padding = dp2px(12); shadowRect.left = shadowRect.left + padding; shadowRect.right = shadowRect.right - padding; shadowRect.top = shadowRect.top + padding; shadowRect.bottom = shadowRect.bottom - padding; }else if (type == THREE_BY_THREE_PIC){ // 3 * 3 方格 int padding = dp2px(10); shadowRect.left = shadowRect.left + padding; shadowRect.right = shadowRect.right - padding; shadowRect.top = shadowRect.top + padding; shadowRect.bottom = shadowRect.bottom -padding; }else if (type == ONE_BY_TWO_PIC){ int padding = dp2px(4); shadowRect.left = shadowRect.left + padding; shadowRect.right = shadowRect.right - padding; } canvas.drawBitmap(shadowBitmap, null, shadowRect, mPhonePaint); } } } @Override public boolean onDrag(View v, DragEvent event) { final int action = event.getAction(); switch(action) { case DragEvent.ACTION_DRAG_STARTED: // 判断是否是需要接收的数据 if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) { Log.e(TAG, "开始拖动"); }else { return false; } break; case DragEvent.ACTION_DRAG_ENTERED: Log.e(TAG, "进入"); mTextView.setVisibility(GONE); isOut = false; break; case DragEvent.ACTION_DRAG_EXITED: Log.e(TAG, "移出"); if (frameLayout.getChildCount() == 0){ mTextView.setVisibility(VISIBLE); } isOut = true; shadowRect = null; invalidate(); break; case DragEvent.ACTION_DRAG_ENDED: Log.e(TAG, "停止拖动"); if (dragView != null && isOut){ mRectList.remove(dragRect); frameLayout.removeView(dragView); } if (frameLayout.getChildCount() == 0){ mTextView.setVisibility(VISIBLE); } dragView = null; dragRect = null; stopDrag(); break; case DragEvent.ACTION_DRAG_LOCATION: // 停留 if (dragView != null){ info = (DraggableInfo) dragView.getTag(); } if (info == null){ break; } compute(info.getType(), mRect, event); adjust(info.getType(), mRect, event); if (isEffectiveArea(mRect) && !isOverlap(mRect)){ shadowRect = mRect; }else { shadowRect = null; } try { Thread.sleep(33); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } invalidate(); break; case DragEvent.ACTION_DROP: Log.e(TAG, "释放拖动"); if (dragView == null){ final DraggableInfo data = (DraggableInfo) event.getClipData().getItemAt(0).getIntent().getSerializableExtra("data"); if (data != null){ int size = mPhoneContentWidth / WIDTH_COUNT - dp2px(10); int padding = size / 4; final ImageView imageView; if (data.getType() == ONE_BY_TWO_PIC){ imageView = new ImageView(getContext()); imageView.setImageResource(data.getPic()); }else { imageView = new DraggableButton(getContext()); imageView.setPadding(padding, padding, padding, padding); if (data.getType() == ONE_BY_ONE_TEXT){ ((DraggableButton)imageView).setText(data.getText()); }else { imageView.setImageResource(data.getPic()); } } imageView.setTag(data); final Rect rect = new Rect(); imageView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { Tools.startDrag(imageView); dragView = imageView; dragRect = rect; setDragInfo(data); imageView.setVisibility(GONE); return false; } }); compute(data.getType(), rect, event); adjust(data.getType(), rect, event); if (isEffectiveArea(rect) && !isOverlap(rect)){ mRectList.add(rect); frameLayout.addView(imageView); } } }else { DraggableInfo data = (DraggableInfo) dragView.getTag(); Rect rect = dragRect; compute(data.getType(), rect, event); adjust(data.getType(), rect, event); if (isEffectiveArea(rect) && !isOverlap(rect)){ dragView.setVisibility(VISIBLE); }else { mRectList.remove(dragRect); frameLayout.removeView(dragView); } dragView = null; dragRect = null; } stopDrag(); break; default: return false; } return true; } private void stopDrag(){ shadowRect = null; info = null; invalidate(); } /** * 是否在有效区域 */ private boolean isEffectiveArea(Rect rect){ return rect.left >= 0 && rect.top >= 0 && rect.right >= 0 && rect.bottom >= 0 && rect.right <= frameLayout.getWidth() && rect.bottom <= frameLayout.getHeight(); } /** * 计算控件位置 */ private void compute(int type, Rect rect, DragEvent event){ int size = mPhoneContentWidth / WIDTH_COUNT - dp2px(10); int x = (int) event.getX(); int y = (int) event.getY(); if (type == ONE_BY_ONE_PIC || type == ONE_BY_ONE_TEXT){ // 1 * 1 方格 rect.left = x - size / 2; rect.top = y - size / 2; rect.right = x + size / 2; rect.bottom = y + size / 2; }else if (type == THREE_BY_THREE_PIC){ // 3 * 3 方格 rect.left = x - size * 3 / 2; rect.top = y - size * 3 / 2; rect.right = x + size * 3 / 2; rect.bottom = y + size * 3 / 2; }else if (type == ONE_BY_TWO_PIC){ // 1 * 2 方格 rect.left = x - size / 2; rect.top = y - size; rect.right = x + size / 2; rect.bottom = y + size; } } /** * 调整控件位置 */ private void adjust(int type, Rect rect, DragEvent event){ // 最小单元格宽高 int size = mPhoneContentWidth / WIDTH_COUNT / 2; // 手机屏幕间距 int padding = dp2px(5); // 1 * 1方格宽高 int width = size * 2 - dp2px(10); int offsetX = (rect.left - padding) % size; if (offsetX < size / 2){ rect.left = rect.left + padding - offsetX; }else { rect.left = rect.left + padding - offsetX + size; } int offsetY = (rect.top - padding) % size; if (offsetY < size / 2){ rect.top = rect.top + padding - offsetY; }else { rect.top = rect.top + padding - offsetY + size; } if (type == ONE_BY_ONE_PIC || type == ONE_BY_ONE_TEXT){ rect.right = rect.left + width; rect.bottom = rect.top + width; }else if (type == ONE_BY_TWO_PIC){ rect.top = rect.top + padding; rect.right = rect.left + width; rect.bottom = rect.top + width * 2; }else if (type == THREE_BY_THREE_PIC){ rect.top = rect.top + padding * 2; rect.left = rect.left + padding * 2; rect.right = rect.left + width * 3; rect.bottom = rect.top + width * 3; } //超出部分修正(超出部分) if (rect.right > frameLayout.getWidth() || rect.bottom > frameLayout.getHeight()){ int currentX = (int) event.getX(); int currentY = (int) event.getY(); int centerX = frameLayout.getWidth() / 2; int centerY = frameLayout.getHeight() / 2; if (currentX <= centerX && currentY <= centerY){ //左上角区域 }else if (currentX >= centerX && currentY <= centerY){ //右上角区域 rect.left = rect.left - size; rect.right = rect.right - size; }else if (currentX <= centerX && currentY >= centerY){ //左下角区域 rect.top = rect.top - size; rect.bottom = rect.bottom - size; }else if (currentX >= centerX && currentY >= centerY){ //右下角区域 if (rect.right > frameLayout.getWidth()){ rect.left = rect.left - size; rect.right = rect.right - size; } if (rect.bottom > frameLayout.getHeight()){ rect.top = rect.top - size; rect.bottom = rect.bottom - size; } } } } /** * 判断是否重叠 */ private boolean isOverlap(Rect rect){ for (Rect mRect : mRectList){ if (!isEqual(mRect)){ if (isRectOverlap(mRect, rect)){ return true; } } } return false; } /** * 判断两Rect是否重叠 */ private boolean isRectOverlap(Rect oldRect, Rect newRect) { return (oldRect.right > newRect.left && newRect.right > oldRect.left && oldRect.bottom > newRect.top && newRect.bottom > oldRect.top); } /** * 判断与拖拽的Rect是否相等 */ private boolean isEqual(Rect rect) { if (dragRect == null){ return false; } return (rect.left == dragRect.left && rect.right == dragRect.right && rect.top == dragRect.top && rect.bottom == dragRect.bottom); } /** * 设置拖拽按钮信息 */ public void setDragInfo(DraggableInfo info){ this.info = info; if (info.getType() != ONE_BY_ONE_TEXT){ shadowBitmap = BitmapFactory.decodeResource(getResources(), info.getPic()); } } private int dp2px(int dp) { DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); float density = displayMetrics.scaledDensity; return (int) (dp * density + 0.5f); } }