Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
A
Android_Catering_service
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
姜天宇
Android_Catering_service
Commits
dfa400a3
Commit
dfa400a3
authored
Aug 23, 2024
by
姜天宇
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(v1.0.2): 增加信息页、注册页、裁剪页
1.增加系统信息页 2.增加设置-注册页 3.对接注册、找回接口 4.增加秤盘裁剪页
parent
d1d6a28b
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
2578 additions
and
30 deletions
+2578
-30
CameraxController.java
...a/src/main/java/com/wmdigit/camera/CameraxController.java
+72
-18
CropValue.java
common/src/main/java/com/wmdigit/common/model/CropValue.java
+49
-0
ParcelHelper.java
.../src/main/java/com/wmdigit/common/utils/ParcelHelper.java
+32
-0
BidirectionalDataBindingCropView.java
...mmon/view/imageview/BidirectionalDataBindingCropView.java
+101
-0
CropView.java
...main/java/com/wmdigit/common/view/imageview/CropView.java
+2084
-0
themes.xml
common/src/main/res/values/themes.xml
+39
-0
CropLocalRepository.java
...com/wmdigit/data/mmkv/repository/CropLocalRepository.java
+28
-1
history.txt
history.txt
+1
-1
CameraCropFragment.java
...java/com/wmdigit/setting/fragment/CameraCropFragment.java
+40
-0
SystemInfoFragment.java
...java/com/wmdigit/setting/fragment/SystemInfoFragment.java
+1
-4
CameraCropViewModel.java
...va/com/wmdigit/setting/viewmodel/CameraCropViewModel.java
+45
-0
SystemInfoViewModel.java
...va/com/wmdigit/setting/viewmodel/SystemInfoViewModel.java
+1
-2
fragment_camera_crop.xml
module-setting/src/main/res/layout/fragment_camera_crop.xml
+83
-3
fragment_system_info.xml
module-setting/src/main/res/layout/fragment_system_info.xml
+1
-1
strings.xml
module-setting/src/main/res/values/strings.xml
+1
-0
No files found.
camera/src/main/java/com/wmdigit/camera/CameraxController.java
View file @
dfa400a3
...
...
@@ -12,7 +12,9 @@ import android.util.Size;
import
androidx.camera.camera2.interop.Camera2Interop
;
import
androidx.camera.core.Camera
;
import
androidx.camera.core.CameraInfo
;
import
androidx.camera.core.CameraProvider
;
import
androidx.camera.core.CameraSelector
;
import
androidx.camera.core.CameraState
;
import
androidx.camera.core.ImageAnalysis
;
import
androidx.camera.core.ImageProxy
;
import
androidx.camera.core.Preview
;
...
...
@@ -50,6 +52,10 @@ public class CameraxController {
* 获取相机服务provider
*/
private
ListenableFuture
<
ProcessCameraProvider
>
cameraProviderFutureListener
;
/**
* 相机
*/
private
Camera
camera
;
/**
* 相机的生命周期
*/
...
...
@@ -66,6 +72,14 @@ public class CameraxController {
* 图片分析回调
*/
private
OnImageAnalyzeListener
onImageAnalyzeListener
;
/**
* 帧间隔
*/
private
int
frameInterval
=
5
;
/**
* 相机状态
*/
private
CameraState
cameraState
=
null
;
// private SurfaceTexture mColorSurfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
...
...
@@ -110,6 +124,16 @@ public class CameraxController {
return
this
;
}
/**
* 设置帧间隔
* @param frameInterval
* @return
*/
public
CameraxController
setFrameInterval
(
int
frameInterval
)
{
this
.
frameInterval
=
frameInterval
;
return
this
;
}
/**
* 相机初始化
*/
...
...
@@ -121,8 +145,10 @@ public class CameraxController {
// 创建图像分析器
ImageAnalysis
imageAnalysis
=
createImageAnalysis
();
// 绑定相机
Camera
camera
=
bindCamera
(
cameraProvider
,
imageAnalysis
);
camera
=
bindCamera
(
cameraProvider
,
imageAnalysis
);
if
(
camera
!=
null
)
{
// 禁用自动对焦
camera
.
getCameraControl
().
cancelFocusAndMetering
();
// 观察相机状态
observeCameraLifecycle
(
camera
);
}
...
...
@@ -151,21 +177,10 @@ public class CameraxController {
long
startTime
=
System
.
currentTimeMillis
();
// 处理图片
count
++;
if
(
count
%
5
==
0
)
{
if
(
count
%
frameInterval
==
0
)
{
analyzeImage
(
imageProxy
);
}
imageProxy
.
close
();
// 休眠
/*long costTime = System.currentTimeMillis() - startTime;
// System.out.println("处理时间:" + costTime);
long leftTime = INTERVAL_FRAMES_ANALYZE - costTime;
if (leftTime <= INTERVAL_FRAMES_ANALYZE && leftTime >= 0){
try {
Thread.sleep(leftTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
});
return
imageAnalysis
;
...
...
@@ -224,9 +239,14 @@ public class CameraxController {
*/
private
void
observeCameraLifecycle
(
Camera
camera
){
camera
.
getCameraInfo
().
getCameraState
().
removeObservers
(
lifecycleOwner
);
camera
.
getCameraInfo
().
getCameraState
().
observe
(
lifecycleOwner
,
cameraState
->
{
XLog
.
i
(
"相机状态:"
+
cameraState
.
getType
());
switch
(
cameraState
.
getType
()){
camera
.
getCameraInfo
().
getCameraState
().
observe
(
lifecycleOwner
,
state
->
{
XLog
.
i
(
"相机状态:"
+
state
.
getType
());
cameraState
=
state
;
switch
(
state
.
getType
()){
case
OPEN:
// 相机开启
break
;
case
CLOSED:
// 相机关闭
break
;
...
...
@@ -235,9 +255,43 @@ public class CameraxController {
break
;
}
if
(
cameraS
tate
.
getError
()
!=
null
){
XLog
.
i
(
"相机异常:"
+
cameraS
tate
.
getType
());
if
(
s
tate
.
getError
()
!=
null
){
XLog
.
i
(
"相机异常:"
+
s
tate
.
getType
());
}
});
}
/**
* 相机是否开启
* @return
*/
public
boolean
isOpen
(){
if
(
cameraState
==
null
){
return
false
;
}
if
(
cameraState
.
getType
()
==
CameraState
.
Type
.
OPEN
||
cameraState
.
getType
()
==
CameraState
.
Type
.
OPENING
){
return
true
;
}
return
false
;
}
public
void
release
(){
ProcessCameraProvider
cameraProvider
=
null
;
try
{
cameraProvider
=
cameraProviderFutureListener
.
get
();
}
catch
(
Exception
e
)
{
XLog
.
e
(
e
.
toString
());
}
if
(
camera
!=
null
){
// 移除camera得生命周期监听
camera
.
getCameraInfo
().
getCameraState
().
removeObservers
(
lifecycleOwner
);
}
if
(
cameraProvider
!=
null
){
cameraProvider
.
unbindAll
();
}
onImageAnalyzeListener
=
null
;
cameraState
=
null
;
executors
.
shutdown
();
instance
=
null
;
}
}
common/src/main/java/com/wmdigit/common/model/CropValue.java
0 → 100644
View file @
dfa400a3
package
com
.
wmdigit
.
common
.
model
;
/**
* 裁剪坐标
* @author dizi
*/
public
class
CropValue
{
private
int
left
,
top
,
width
,
height
;
public
CropValue
(
int
left
,
int
top
,
int
width
,
int
height
)
{
this
.
left
=
left
;
this
.
top
=
top
;
this
.
width
=
width
;
this
.
height
=
height
;
}
public
int
getLeft
()
{
return
left
;
}
public
void
setLeft
(
int
left
)
{
this
.
left
=
left
;
}
public
int
getTop
()
{
return
top
;
}
public
void
setTop
(
int
top
)
{
this
.
top
=
top
;
}
public
int
getWidth
()
{
return
width
;
}
public
void
setWidth
(
int
width
)
{
this
.
width
=
width
;
}
public
int
getHeight
()
{
return
height
;
}
public
void
setHeight
(
int
height
)
{
this
.
height
=
height
;
}
}
common/src/main/java/com/wmdigit/common/utils/ParcelHelper.java
0 → 100644
View file @
dfa400a3
package
com
.
wmdigit
.
common
.
utils
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
/**
* @author dizi
*/
public
class
ParcelHelper
{
/**
* 深度拷贝
* @param input
* @return
* @param <T>
*/
public
static
<
T
>
T
copy
(
Parcelable
input
)
{
Parcel
parcel
=
null
;
try
{
parcel
=
Parcel
.
obtain
();
parcel
.
writeParcelable
(
input
,
0
);
parcel
.
setDataPosition
(
0
);
return
parcel
.
readParcelable
(
input
.
getClass
().
getClassLoader
());
}
finally
{
if
(
parcel
!=
null
){
parcel
.
recycle
();
}
}
}
}
common/src/main/java/com/wmdigit/common/view/imageview/BidirectionalDataBindingCropView.java
0 → 100644
View file @
dfa400a3
package
com
.
wmdigit
.
common
.
view
.
imageview
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.graphics.Rect
;
import
android.util.AttributeSet
;
import
androidx.databinding.BindingAdapter
;
import
androidx.databinding.InverseBindingAdapter
;
import
androidx.databinding.InverseBindingListener
;
import
com.elvishew.xlog.XLog
;
import
com.wmdigit.common.model.CropValue
;
/**
* 支持双向数据绑定的CropView
* @author dizi
*/
public
class
BidirectionalDataBindingCropView
extends
CropView
{
private
static
InverseBindingListener
inverseBindingListener
;
public
BidirectionalDataBindingCropView
(
Context
context
)
{
super
(
context
);
this
.
setOnActionUpListener
(
rect
->
{
if
(
inverseBindingListener
!=
null
){
inverseBindingListener
.
onChange
();
}
});
}
public
BidirectionalDataBindingCropView
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
this
.
setOnActionUpListener
(
rect
->
{
if
(
inverseBindingListener
!=
null
){
inverseBindingListener
.
onChange
();
}
});
}
public
BidirectionalDataBindingCropView
(
Context
context
,
AttributeSet
attrs
,
int
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
);
this
.
setOnActionUpListener
(
rect
->
{
if
(
inverseBindingListener
!=
null
){
inverseBindingListener
.
onChange
();
}
});
}
@Override
protected
void
onDetachedFromWindow
()
{
super
.
onDetachedFromWindow
();
inverseBindingListener
=
null
;
this
.
setOnActionUpListener
(
null
);
}
/**
* 设置图片帧
* @param view
* @param bitmap
*/
@BindingAdapter
(
"frame"
)
public
static
void
setFrame
(
CropView
view
,
Bitmap
bitmap
){
if
(
bitmap
!=
null
&&
!
bitmap
.
isRecycled
()){
view
.
setImageBitmap
(
bitmap
);
}
}
/**
* 预设裁剪坐标
* @param view
* @param value
*/
@BindingAdapter
(
"preset_crop_value"
)
public
static
void
presetCropValue
(
CropView
view
,
CropValue
value
){
if
(
value
!=
null
){
view
.
setPresetCropCoordinate
(
new
Rect
(
value
.
getLeft
(),
value
.
getTop
(),
value
.
getWidth
()
+
value
.
getLeft
(),
value
.
getHeight
()
+
value
.
getTop
()));
}
}
@InverseBindingAdapter
(
attribute
=
"preset_crop_value"
,
event
=
"preset_crop_valueAttrChanged"
)
public
static
CropValue
getCropValue
(
CropView
view
){
Rect
rect
=
view
.
getCropRect
();
CropValue
cropValue
=
new
CropValue
(
0
,
0
,
0
,
0
);
if
(
rect
!=
null
){
cropValue
.
setLeft
(
rect
.
left
);
cropValue
.
setTop
(
rect
.
top
);
cropValue
.
setWidth
(
rect
.
width
());
cropValue
.
setHeight
(
rect
.
height
());
}
return
cropValue
;
}
@BindingAdapter
(
value
=
{
"preset_crop_valueAttrChanged"
})
public
static
void
setCropValueChanged
(
CropView
view
,
InverseBindingListener
listener
){
if
(
listener
!=
null
)
{
inverseBindingListener
=
listener
;
}
}
}
common/src/main/java/com/wmdigit/common/view/imageview/CropView.java
0 → 100644
View file @
dfa400a3
package
com
.
wmdigit
.
common
.
view
.
imageview
;
import
android.animation.Animator
;
import
android.animation.AnimatorListenerAdapter
;
import
android.animation.ValueAnimator
;
import
android.content.Context
;
import
android.content.res.TypedArray
;
import
android.graphics.Bitmap
;
import
android.graphics.Canvas
;
import
android.graphics.Matrix
;
import
android.graphics.Paint
;
import
android.graphics.Path
;
import
android.graphics.PointF
;
import
android.graphics.Rect
;
import
android.graphics.RectF
;
import
android.graphics.drawable.BitmapDrawable
;
import
android.graphics.drawable.Drawable
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
android.util.AttributeSet
;
import
android.util.Log
;
import
android.view.MotionEvent
;
import
android.view.animation.DecelerateInterpolator
;
import
androidx.annotation.ColorInt
;
import
androidx.annotation.UiThread
;
import
androidx.appcompat.widget.AppCompatImageView
;
import
com.wmdigit.common.R
;
import
com.wmdigit.common.utils.SizeUtils
;
import
io.reactivex.Observable
;
import
io.reactivex.ObservableEmitter
;
import
io.reactivex.ObservableOnSubscribe
;
import
io.reactivex.Observer
;
import
io.reactivex.android.schedulers.AndroidSchedulers
;
import
io.reactivex.disposables.Disposable
;
import
io.reactivex.schedulers.Schedulers
;
public
class
CropView
extends
AppCompatImageView
{
private
final
static
String
TAG
=
CropView
.
class
.
getSimpleName
();
private
final
static
int
HANDLE_SIZE
=
24
;
private
final
static
int
HANDLE_WIDTH
=
2
;
private
final
static
int
FRAME_MIN_SIZE
=
50
;
private
final
static
int
FRAME_STROKE_WEIGHT
=
1
;
private
final
static
int
GUIDE_STROKE_WEIGHT
=
1
;
private
final
static
float
DEFAULT_INITIAL_SCALE
=
1.0f
;
// 颜色值,包含透明度
private
static
final
int
TRANSPARENT
=
0x00000000
;
private
static
final
int
TRANSLUCENT_WHITE
=
0xBBFFFFFF
;
private
static
final
int
WHITE
=
0xFFFFFFFF
;
private
static
final
int
TRANSLUCENT_BLACK
=
0xBB000000
;
private
static
final
int
ORANGE
=
0xFFF19520
;
// 默认的动画时长
private
static
final
int
DEFAULT_ANIMATION_DURATION_MILLIS
=
100
;
// view的宽高
private
int
mViewWidth
=
0
;
private
int
mViewHeight
=
0
;
// 放大比例
private
float
mCropScale
=
1.0f
;
// 旋转角度
private
float
mImgAngle
=
0.0f
;
// 图片宽度
private
float
mImgWidth
=
0.0f
;
// 图片高度
private
float
mImgHeight
=
0.0f
;
// 四角线的长度值
private
int
mHandleSize
;
// 四角的线宽度
private
int
mHandleWidth
;
// 触摸可处理的宽度值
private
int
mTouchPadding
=
0
;
// 裁剪框最小值
private
int
mFrameMinSize
;
// 裁剪外框线框宽度
private
float
mFrameStrokeWeight
=
2
f
;
// 裁剪指导线宽度
private
float
mGuideStrokeWeight
=
2
f
;
// 准星线宽度
private
float
mFrontSightWeight
=
4
f
;
// 外框画笔
private
Paint
mFramePaint
;
// 半透明区域画笔
private
Paint
mTranslucentPaint
;
// bitmap画笔
private
Paint
mBitmapPaint
;
// 准星画笔
private
Paint
mFrontSightPaint
;
// 背景的颜色
private
int
mBackgroundColor
;
// 半透明框的颜色
private
int
mOverlayColor
;
// 外框的颜色
private
int
mFrameColor
;
// 四边角点的颜色
private
int
mHandleColor
;
// 指导线颜色
private
int
mGuideColor
;
// 准星颜色
private
int
mFrontSightColor
;
// 裁剪模式
private
CropModeEnum
mCropMode
=
CropModeEnum
.
RATIO_2_3
;
// 指导线的显示模式
private
ShowModeEnum
mGuideShowMode
=
ShowModeEnum
.
SHOW_ALWAYS
;
// 处理四个角的显示模式
private
ShowModeEnum
mHandleShowMode
=
ShowModeEnum
.
SHOW_ALWAYS
;
// 触摸的情况
private
TouchAreaEnum
mTouchArea
=
TouchAreaEnum
.
OUT_OF_BOUNDS
;
// 居中中点的矩型区域
private
PointF
mCenter
=
new
PointF
();
// 图形的矩阵类
private
Matrix
mMatrix
=
null
;
// 图片的rectf区域
private
RectF
mImageRectF
;
// 裁剪框的RectF
private
RectF
mFrameRectF
;
// 初始化的比例大小0.01到1.0,默认情况为0.75
private
float
mInitialFrameScale
;
// 是否初始化
private
boolean
mIsInitialized
;
// 是否开始裁剪
private
boolean
mIsCropEnabled
=
true
;
// 是否显示指导线
private
boolean
mShowGuide
=
true
;
// 是否显示处理线
private
boolean
mShowHandle
=
true
;
private
float
mLastX
,
mLastY
;
// 旋转的动画
private
ValueAnimator
mValueAnimator
=
null
;
// 裁剪框放大的动画
private
ValueAnimator
mFrameValueAnimator
=
null
;
private
boolean
mIsAnimationEnabled
=
true
;
private
int
mAnimationDurationMillis
=
DEFAULT_ANIMATION_DURATION_MILLIS
;
private
boolean
mIsRotating
=
false
;
private
boolean
mIsAnimating
=
false
;
private
OnCropListener
mOnCropListener
;
// 是否已经翻转了
private
boolean
mIsReverseY
=
false
;
// 是否有旋转操作
private
boolean
mIsRotated
=
false
;
// 旋转操作的开关
private
boolean
mRotateSwitch
=
true
;
private
Rect
cropRect
;
private
Rect
presetCropCoordinate
=
null
;
// 是否允许移动裁剪框
private
boolean
moveCropRectEnable
=
true
;
public
interface
OnActionUpListener
{
void
onActionUp
(
Rect
rect
);
}
private
OnActionUpListener
onActionUpListener
;
public
void
setOnActionUpListener
(
OnActionUpListener
onActionUpListener
)
{
this
.
onActionUpListener
=
onActionUpListener
;
}
public
CropView
(
Context
context
)
{
this
(
context
,
null
);
}
public
CropView
(
Context
context
,
AttributeSet
attrs
)
{
this
(
context
,
attrs
,
0
);
}
public
CropView
(
Context
context
,
AttributeSet
attrs
,
int
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
);
mHandleSize
=
SizeUtils
.
dp2px
(
HANDLE_SIZE
);
mFrameMinSize
=
SizeUtils
.
dp2px
(
FRAME_MIN_SIZE
);
mFrameStrokeWeight
=
SizeUtils
.
dp2px
(
FRAME_STROKE_WEIGHT
);
mGuideStrokeWeight
=
SizeUtils
.
dp2px
(
GUIDE_STROKE_WEIGHT
);
mFramePaint
=
new
Paint
(
Paint
.
ANTI_ALIAS_FLAG
);
mTranslucentPaint
=
new
Paint
(
Paint
.
ANTI_ALIAS_FLAG
);
mBitmapPaint
=
new
Paint
(
Paint
.
ANTI_ALIAS_FLAG
);
mFrontSightPaint
=
new
Paint
(
Paint
.
ANTI_ALIAS_FLAG
);
// 抗锯齿处理
mBitmapPaint
.
setFilterBitmap
(
true
);
mMatrix
=
new
Matrix
();
mCropScale
=
1.0f
;
mBackgroundColor
=
TRANSPARENT
;
mFrameColor
=
WHITE
;
mOverlayColor
=
TRANSLUCENT_BLACK
;
mHandleColor
=
WHITE
;
mGuideColor
=
TRANSLUCENT_WHITE
;
mFrontSightColor
=
ORANGE
;
loadStyleable
(
context
,
attrs
,
defStyleAttr
);
mValueAnimator
=
ValueAnimator
.
ofFloat
(
0
,
1
f
);
mValueAnimator
.
setInterpolator
(
new
DecelerateInterpolator
());
mValueAnimator
.
setDuration
(
mAnimationDurationMillis
);
mFrameValueAnimator
=
ValueAnimator
.
ofFloat
(
0
,
1
f
);
mFrameValueAnimator
.
setInterpolator
(
new
DecelerateInterpolator
());
mFrameValueAnimator
.
setDuration
(
mAnimationDurationMillis
);
}
public
void
setBgColor
(
@ColorInt
int
backgroundColor
)
{
mBackgroundColor
=
backgroundColor
;
invalidate
();
}
/**
* 加载Style自定义属性数据
*
* @param context
* @param attrs
* @param defStyleAttr
*/
private
void
loadStyleable
(
Context
context
,
AttributeSet
attrs
,
int
defStyleAttr
)
{
TypedArray
ta
=
context
.
obtainStyledAttributes
(
attrs
,
R
.
styleable
.
CropView
,
defStyleAttr
,
0
);
Drawable
drawable
;
mCropMode
=
CropModeEnum
.
SQUARE
;
try
{
drawable
=
ta
.
getDrawable
(
R
.
styleable
.
CropView_img_src
);
if
(
drawable
!=
null
)
setImageDrawable
(
drawable
);
for
(
CropModeEnum
mode
:
CropModeEnum
.
values
())
{
if
(
ta
.
getInt
(
R
.
styleable
.
CropView_crop_mode
,
3
)
==
mode
.
getID
())
{
mCropMode
=
mode
;
break
;
}
}
mBackgroundColor
=
ta
.
getColor
(
R
.
styleable
.
CropView_background_color
,
TRANSPARENT
);
mOverlayColor
=
ta
.
getColor
(
R
.
styleable
.
CropView_overlay_color
,
TRANSLUCENT_BLACK
);
mFrameColor
=
ta
.
getColor
(
R
.
styleable
.
CropView_framecolor
,
WHITE
);
mHandleColor
=
ta
.
getColor
(
R
.
styleable
.
CropView_handle_color
,
WHITE
);
mGuideColor
=
ta
.
getColor
(
R
.
styleable
.
CropView_guide_color
,
TRANSLUCENT_WHITE
);
for
(
ShowModeEnum
mode
:
ShowModeEnum
.
values
())
{
if
(
ta
.
getInt
(
R
.
styleable
.
CropView_guide_show_mode
,
1
)
==
mode
.
getId
())
{
mGuideShowMode
=
mode
;
break
;
}
}
for
(
ShowModeEnum
mode
:
ShowModeEnum
.
values
())
{
if
(
ta
.
getInt
(
R
.
styleable
.
CropView_handle_show_mode
,
1
)
==
mode
.
getId
())
{
mHandleShowMode
=
mode
;
break
;
}
}
setGuideShowMode
(
mGuideShowMode
);
setHandleShowMode
(
mHandleShowMode
);
mHandleSize
=
ta
.
getDimensionPixelSize
(
R
.
styleable
.
CropView_handle_size
,
SizeUtils
.
dp2px
(
HANDLE_SIZE
));
mHandleWidth
=
ta
.
getDimensionPixelSize
(
R
.
styleable
.
CropView_handle_width
,
SizeUtils
.
dp2px
(
HANDLE_WIDTH
));
mTouchPadding
=
ta
.
getDimensionPixelSize
(
R
.
styleable
.
CropView_touch_padding
,
0
);
mFrameMinSize
=
ta
.
getDimensionPixelSize
(
R
.
styleable
.
CropView_min_frame_size
,
SizeUtils
.
dp2px
(
FRAME_MIN_SIZE
));
mFrameStrokeWeight
=
ta
.
getDimensionPixelSize
(
R
.
styleable
.
CropView_frame_stroke_weight
,
SizeUtils
.
dp2px
(
FRAME_STROKE_WEIGHT
));
mGuideStrokeWeight
=
ta
.
getDimensionPixelSize
(
R
.
styleable
.
CropView_guide_stroke_weight
,
SizeUtils
.
dp2px
(
GUIDE_STROKE_WEIGHT
));
mIsCropEnabled
=
ta
.
getBoolean
(
R
.
styleable
.
CropView_crop_enabled
,
true
);
mInitialFrameScale
=
constrain
(
ta
.
getFloat
(
R
.
styleable
.
CropView_initial_frame_scale
,
DEFAULT_INITIAL_SCALE
),
0.01f
,
1.0f
,
DEFAULT_INITIAL_SCALE
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
finally
{
ta
.
recycle
();
}
}
/**
* 设置是否允许移动裁剪框
* @param mIsAllowMoveCropRect
*/
public
void
setMoveCropRect
(
boolean
mIsAllowMoveCropRect
)
{
this
.
moveCropRectEnable
=
mIsAllowMoveCropRect
;
}
/**
* 设置指导线的显示模式
*
* @param guideShowMode
*/
public
void
setGuideShowMode
(
ShowModeEnum
guideShowMode
)
{
mGuideShowMode
=
guideShowMode
;
switch
(
guideShowMode
)
{
case
SHOW_ALWAYS:
mShowGuide
=
true
;
break
;
case
NOT_SHOW:
case
SHOW_ON_TOUCH:
mShowGuide
=
false
;
break
;
}
invalidate
();
}
/**
* 设置处理线的显示模式
*
* @param mode
*/
public
void
setHandleShowMode
(
ShowModeEnum
mode
)
{
mHandleShowMode
=
mode
;
switch
(
mode
)
{
case
SHOW_ALWAYS:
mShowHandle
=
true
;
break
;
case
NOT_SHOW:
case
SHOW_ON_TOUCH:
mShowHandle
=
false
;
break
;
}
invalidate
();
}
@Override
protected
void
onMeasure
(
int
widthMeasureSpec
,
int
heightMeasureSpec
)
{
final
int
viewWidth
=
MeasureSpec
.
getSize
(
widthMeasureSpec
);
final
int
viewHeight
=
MeasureSpec
.
getSize
(
heightMeasureSpec
);
setMeasuredDimension
(
viewWidth
,
viewHeight
);
mViewWidth
=
viewWidth
-
getPaddingLeft
()
-
getPaddingRight
();
mViewHeight
=
viewHeight
-
getPaddingTop
()
-
getPaddingBottom
();
}
@Override
protected
void
onLayout
(
boolean
changed
,
int
left
,
int
top
,
int
right
,
int
bottom
)
{
if
(
getDrawable
()
!=
null
)
{
doLayout
(
mViewWidth
,
mViewHeight
);
}
}
@Override
protected
void
onDraw
(
Canvas
canvas
)
{
canvas
.
drawColor
(
mBackgroundColor
);
if
(
mIsInitialized
)
{
setMatrix
();
Bitmap
bitmap
=
getBitmap
();
if
(
bitmap
!=
null
)
{
canvas
.
drawBitmap
(
bitmap
,
mMatrix
,
mBitmapPaint
);
// 画裁剪框
drawCropFrame
(
canvas
);
}
}
}
/**
* 画裁剪框
*
* @param canvas
*/
private
void
drawCropFrame
(
Canvas
canvas
)
{
if
(!
mIsCropEnabled
)
{
// 如果不是裁剪模式,直接就不画裁剪框
return
;
}
if
(
mIsRotating
)
{
// 如果正在旋转,不画裁剪框
return
;
}
drawOverlay
(
canvas
);
drawFrame
(
canvas
);
drawFrontSight
(
canvas
);
if
(
mShowGuide
)
{
drawGuidelines
(
canvas
);
}
if
(
mShowHandle
)
{
drawHandleLines
(
canvas
);
}
}
/**
* 绘制文字
* @param canvas
* @param text
*/
private
void
drawText
(
Canvas
canvas
,
String
text
){
}
/**
* 画四角的线
*
* @param canvas
*/
private
void
drawHandleLines
(
Canvas
canvas
)
{
mFramePaint
.
setColor
(
mHandleColor
);
mFramePaint
.
setStyle
(
Paint
.
Style
.
FILL
);
// 指导线最边界(最左/最右/最下/最上)的x和y
float
handleLineLeftX
=
mFrameRectF
.
left
-
mHandleWidth
;
float
handleLineRightX
=
mFrameRectF
.
right
+
mHandleWidth
;
float
handleLineTopY
=
mFrameRectF
.
top
-
mHandleWidth
;
float
handleLineBottomY
=
mFrameRectF
.
bottom
+
mHandleWidth
;
// 左上竖向
RectF
ltRectFVertical
=
new
RectF
(
handleLineLeftX
,
handleLineTopY
,
mFrameRectF
.
left
,
handleLineTopY
+
mHandleSize
);
// 左上横向
RectF
ltRectFHorizontal
=
new
RectF
(
handleLineLeftX
,
handleLineTopY
,
handleLineLeftX
+
mHandleSize
,
mFrameRectF
.
top
);
RectF
rtRectFHorizontal
=
new
RectF
(
handleLineRightX
-
mHandleSize
,
handleLineTopY
,
handleLineRightX
,
mFrameRectF
.
top
);
RectF
rtRectFVertical
=
new
RectF
(
mFrameRectF
.
right
,
handleLineTopY
,
handleLineRightX
,
handleLineTopY
+
mHandleSize
);
RectF
lbRectFVertical
=
new
RectF
(
handleLineLeftX
,
handleLineBottomY
-
mHandleSize
,
mFrameRectF
.
left
,
mFrameRectF
.
bottom
);
RectF
lbRectFHorizontal
=
new
RectF
(
handleLineLeftX
,
mFrameRectF
.
bottom
,
handleLineLeftX
+
mHandleSize
,
handleLineBottomY
);
RectF
rbRectFVertical
=
new
RectF
(
mFrameRectF
.
right
,
handleLineBottomY
-
mHandleSize
,
handleLineRightX
,
handleLineBottomY
);
RectF
rbRectFHorizontal
=
new
RectF
(
handleLineRightX
-
mHandleSize
,
mFrameRectF
.
bottom
,
handleLineRightX
,
handleLineBottomY
);
canvas
.
drawRect
(
ltRectFVertical
,
mFramePaint
);
canvas
.
drawRect
(
ltRectFHorizontal
,
mFramePaint
);
canvas
.
drawRect
(
rtRectFVertical
,
mFramePaint
);
canvas
.
drawRect
(
rtRectFHorizontal
,
mFramePaint
);
canvas
.
drawRect
(
lbRectFVertical
,
mFramePaint
);
canvas
.
drawRect
(
lbRectFHorizontal
,
mFramePaint
);
canvas
.
drawRect
(
rbRectFVertical
,
mFramePaint
);
canvas
.
drawRect
(
rbRectFHorizontal
,
mFramePaint
);
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
// 如果当前是自由模式
mFramePaint
.
setStrokeCap
(
Paint
.
Cap
.
ROUND
);
mFramePaint
.
setStrokeWidth
(
mHandleWidth
+
SizeUtils
.
dp2px
(
2
));
float
centerX
=
mFrameRectF
.
left
+
mFrameRectF
.
width
()
/
2
;
float
centerY
=
mFrameRectF
.
top
+
mFrameRectF
.
height
()
/
2
;
canvas
.
drawLine
(
centerX
-
mHandleSize
,
mFrameRectF
.
top
,
(
centerX
+
mHandleSize
),
mFrameRectF
.
top
,
mFramePaint
);
canvas
.
drawLine
(
centerX
-
mHandleSize
,
mFrameRectF
.
bottom
,
centerX
+
mHandleSize
,
mFrameRectF
.
bottom
,
mFramePaint
);
canvas
.
drawLine
(
mFrameRectF
.
left
,
(
centerY
-
mHandleSize
),
mFrameRectF
.
left
,
centerY
+
mHandleSize
,
mFramePaint
);
canvas
.
drawLine
(
mFrameRectF
.
right
,
centerY
-
mHandleSize
,
mFrameRectF
.
right
,
centerY
+
mHandleSize
,
mFramePaint
);
}
}
/**
* 画准星
* @param canvas
*/
private
void
drawFrontSight
(
Canvas
canvas
){
mFrontSightPaint
.
setColor
(
mFrontSightColor
);
mFrontSightPaint
.
setStrokeWidth
(
mFrontSightWeight
);
int
frontSightWidth
=
30
;
int
frontSightHeight
=
30
;
// 准星水平线的起始坐标
float
x1
=
mFrameRectF
.
left
+
(
mFrameRectF
.
width
()
/
2.0f
)
-
frontSightWidth
/
2.0f
;
float
y1
=
mFrameRectF
.
top
+
(
mFrameRectF
.
height
()/
2.0f
);
// 准星水平线的终点坐标
float
x2
=
mFrameRectF
.
left
+
(
mFrameRectF
.
width
()
/
2.0f
)
+
frontSightWidth
/
2.0f
;
float
y2
=
mFrameRectF
.
top
+
(
mFrameRectF
.
height
()/
2.0f
);
// 准星垂直线的顶部坐标
float
x3
=
mFrameRectF
.
left
+
(
mFrameRectF
.
width
()
/
2.0f
);
float
y3
=
mFrameRectF
.
top
+
(
mFrameRectF
.
height
()/
2.0f
)
-
frontSightHeight
/
2.0f
;
// 准星垂直线的底部坐标
float
x4
=
mFrameRectF
.
left
+
(
mFrameRectF
.
width
()
/
2.0f
);
float
y4
=
mFrameRectF
.
top
+
(
mFrameRectF
.
height
()/
2.0f
)
+
frontSightHeight
/
2.0f
;
// 画水平线
canvas
.
drawLine
(
x1
,
y1
,
x2
,
y2
,
mFrontSightPaint
);
// 画垂直线
canvas
.
drawLine
(
x3
,
y3
,
x4
,
y4
,
mFrontSightPaint
);
}
/**
* 画指导线
*
* @param canvas
*/
private
void
drawGuidelines
(
Canvas
canvas
)
{
mFramePaint
.
setColor
(
mGuideColor
);
mFramePaint
.
setStrokeWidth
(
mGuideStrokeWeight
);
// 从左往右第一个竖线的横坐标
float
x1
=
mFrameRectF
.
left
+
(
mFrameRectF
.
right
-
mFrameRectF
.
left
)
/
3.0f
;
float
x2
=
mFrameRectF
.
right
-
(
mFrameRectF
.
right
-
mFrameRectF
.
left
)
/
3.0f
;
// 从上往下第一个横的y坐标
float
y1
=
mFrameRectF
.
top
+
(
mFrameRectF
.
bottom
-
mFrameRectF
.
top
)
/
3.0f
;
float
y2
=
mFrameRectF
.
bottom
-
(
mFrameRectF
.
bottom
-
mFrameRectF
.
top
)
/
3.0f
;
// 画竖线
canvas
.
drawLine
(
x1
,
mFrameRectF
.
top
,
x1
,
mFrameRectF
.
bottom
,
mFramePaint
);
canvas
.
drawLine
(
x2
,
mFrameRectF
.
top
,
x2
,
mFrameRectF
.
bottom
,
mFramePaint
);
// 画横线
canvas
.
drawLine
(
mFrameRectF
.
left
,
y1
,
mFrameRectF
.
right
,
y1
,
mFramePaint
);
canvas
.
drawLine
(
mFrameRectF
.
left
,
y2
,
mFrameRectF
.
right
,
y2
,
mFramePaint
);
}
/**
* 画裁剪框边界线
*
* @param canvas
*/
private
void
drawFrame
(
Canvas
canvas
)
{
mFramePaint
.
setStyle
(
Paint
.
Style
.
STROKE
);
mFramePaint
.
setFilterBitmap
(
true
);
mFramePaint
.
setColor
(
mFrameColor
);
mFramePaint
.
setStrokeWidth
(
mFrameStrokeWeight
);
canvas
.
drawRect
(
mFrameRectF
,
mFramePaint
);
}
/**
* 画裁剪框的半透明覆盖层
*
* @param canvas
*/
private
void
drawOverlay
(
Canvas
canvas
)
{
mTranslucentPaint
.
setStyle
(
Paint
.
Style
.
FILL
);
mTranslucentPaint
.
setFilterBitmap
(
true
);
mTranslucentPaint
.
setColor
(
mOverlayColor
);
Path
path
=
new
Path
();
RectF
overlayRectF
=
new
RectF
();
overlayRectF
.
set
(
mImageRectF
);
path
.
addRect
(
mFrameRectF
,
Path
.
Direction
.
CW
);
path
.
addRect
(
overlayRectF
,
Path
.
Direction
.
CCW
);
canvas
.
drawPath
(
path
,
mTranslucentPaint
);
}
@Override
public
boolean
onTouchEvent
(
MotionEvent
event
)
{
// 如果还没初始化,直接跳过触摸事件
if
(!
mIsInitialized
)
{
return
false
;
}
// 如果没有打开裁剪功能,那么也直接跳过触摸事件
if
(!
mIsCropEnabled
)
{
return
false
;
}
// 如果是在旋转,跳过触摸事件
if
(
mIsRotating
)
{
return
false
;
}
// 如果是在切换裁剪模式,跳过触摸事件
if
(
mIsAnimating
)
{
return
false
;
}
// 如果不允许移动裁剪窗口,跳过触摸事件
if
(!
moveCropRectEnable
){
return
false
;
}
switch
(
event
.
getAction
())
{
case
MotionEvent
.
ACTION_DOWN
:
{
onActionDown
(
event
);
return
true
;
}
case
MotionEvent
.
ACTION_MOVE
:
{
onActionMove
(
event
);
if
(
mTouchArea
!=
TouchAreaEnum
.
OUT_OF_BOUNDS
)
{
// 阻止父view拦截点击事件
getParent
().
requestDisallowInterceptTouchEvent
(
true
);
}
return
true
;
}
case
MotionEvent
.
ACTION_CANCEL
:
{
getParent
().
requestDisallowInterceptTouchEvent
(
true
);
onActionCancel
();
return
true
;
}
case
MotionEvent
.
ACTION_UP
:
{
getParent
().
requestDisallowInterceptTouchEvent
(
true
);
onActionUp
();
return
true
;
}
default
:
{
break
;
}
}
return
false
;
}
private
void
onActionUp
()
{
if
(
mGuideShowMode
==
ShowModeEnum
.
SHOW_ON_TOUCH
)
{
mShowGuide
=
false
;
}
if
(
mHandleShowMode
==
ShowModeEnum
.
SHOW_ON_TOUCH
){
mShowHandle
=
false
;
}
mTouchArea
=
TouchAreaEnum
.
OUT_OF_BOUNDS
;
// 回调通知手部抬起
if
(
onActionUpListener
!=
null
){
cropRect
=
null
;
// 计算矩形
Bitmap
source
=
getBitmap
();
if
(
source
!=
null
&&
!
source
.
isRecycled
()){
cropRect
=
calcCropRect
(
source
.
getWidth
(),
source
.
getHeight
());
}
onActionUpListener
.
onActionUp
(
cropRect
);
}
invalidate
();
}
private
void
onActionCancel
()
{
mTouchArea
=
TouchAreaEnum
.
OUT_OF_BOUNDS
;
invalidate
();
}
private
void
onActionMove
(
MotionEvent
event
)
{
float
diffX
=
event
.
getX
()
-
mLastX
;
float
diffY
=
event
.
getY
()
-
mLastY
;
// 区分点击的区域进行移动
switch
(
mTouchArea
)
{
case
CENTER:
{
moveFrame
(
diffX
,
diffY
);
break
;
}
case
LEFT_TOP:
{
moveHandleLeftTop
(
diffX
,
diffY
);
break
;
}
case
RIGHT_TOP:
{
moveHandleRightTop
(
diffX
,
diffY
);
break
;
}
case
LEFT_BOTTOM:
{
moveHandleLeftBottom
(
diffX
,
diffY
);
break
;
}
case
RIGHT_BOTTOM:
{
moveHandleRightBottom
(
diffX
,
diffY
);
break
;
}
case
CENTER_LEFT:
{
moveHandleCenterLeft
(
diffX
);
break
;
}
case
CENTER_TOP:
{
moveHandleCenterTop
(
diffY
);
break
;
}
case
CENTER_RIGHT:
{
moveHandleCenterRight
(
diffX
);
break
;
}
case
CENTER_BOTTOM:
{
moveHandleCenterBottom
(
diffY
);
break
;
}
case
OUT_OF_BOUNDS:
{
break
;
}
}
invalidate
();
mLastX
=
event
.
getX
();
mLastY
=
event
.
getY
();
}
private
void
moveHandleCenterBottom
(
float
diffY
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
bottom
+=
diffY
;
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
bottom
+=
offsetY
;
}
checkScaleBounds
();
}
}
private
void
moveHandleCenterRight
(
float
diffX
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
right
+=
diffX
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
right
+=
offsetX
;
}
checkScaleBounds
();
}
}
private
void
moveHandleCenterTop
(
float
diffY
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
top
+=
diffY
;
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
top
-=
offsetY
;
}
checkScaleBounds
();
}
}
private
void
moveHandleCenterLeft
(
float
diffX
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
left
+=
diffX
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
left
-=
offsetX
;
}
checkScaleBounds
();
}
}
private
void
moveHandleRightTop
(
float
diffX
,
float
diffY
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
right
+=
diffX
;
mFrameRectF
.
top
+=
diffY
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
right
+=
offsetX
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
top
-=
offsetY
;
}
checkScaleBounds
();
}
else
{
float
dx
=
diffX
;
float
dy
=
diffX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
right
+=
dx
;
mFrameRectF
.
top
-=
dy
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
right
+=
offsetX
;
float
offsetY
=
offsetX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
top
-=
offsetY
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
top
-=
offsetY
;
float
offsetX
=
offsetY
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
right
+=
offsetX
;
}
float
ox
,
oy
;
if
(!
isInsideX
(
mFrameRectF
.
right
))
{
ox
=
mFrameRectF
.
right
-
mImageRectF
.
right
;
mFrameRectF
.
right
-=
ox
;
oy
=
ox
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
top
+=
oy
;
}
if
(!
isInsideY
(
mFrameRectF
.
top
))
{
oy
=
mImageRectF
.
top
-
mFrameRectF
.
top
;
mFrameRectF
.
top
+=
oy
;
ox
=
oy
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
right
-=
ox
;
}
}
}
private
void
moveHandleLeftBottom
(
float
diffX
,
float
diffY
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
left
+=
diffX
;
mFrameRectF
.
bottom
+=
diffY
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
left
-=
offsetX
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
bottom
+=
offsetY
;
}
checkScaleBounds
();
}
else
{
float
dx
=
diffX
;
float
dy
=
diffX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
left
+=
dx
;
mFrameRectF
.
bottom
-=
dy
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
left
-=
offsetX
;
float
offsetY
=
offsetX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
bottom
+=
offsetY
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
bottom
+=
offsetY
;
float
offsetX
=
offsetY
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
left
-=
offsetX
;
}
float
ox
,
oy
;
if
(!
isInsideX
(
mFrameRectF
.
left
))
{
ox
=
mImageRectF
.
left
-
mFrameRectF
.
left
;
mFrameRectF
.
left
+=
ox
;
oy
=
ox
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
bottom
-=
oy
;
}
if
(!
isInsideY
(
mFrameRectF
.
bottom
))
{
oy
=
mFrameRectF
.
bottom
-
mImageRectF
.
bottom
;
mFrameRectF
.
bottom
-=
oy
;
ox
=
oy
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
left
+=
ox
;
}
}
}
private
void
moveHandleRightBottom
(
float
diffX
,
float
diffY
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
right
+=
diffX
;
mFrameRectF
.
bottom
+=
diffY
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
right
+=
offsetX
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
bottom
+=
offsetY
;
}
checkScaleBounds
();
}
else
{
float
dx
=
diffX
;
float
dy
=
diffX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
right
+=
dx
;
mFrameRectF
.
bottom
+=
dy
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
right
+=
offsetX
;
float
offsetY
=
offsetX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
bottom
+=
offsetY
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
bottom
+=
offsetY
;
float
offsetX
=
offsetY
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
right
+=
offsetX
;
}
float
ox
,
oy
;
if
(!
isInsideX
(
mFrameRectF
.
right
))
{
ox
=
mFrameRectF
.
right
-
mImageRectF
.
right
;
mFrameRectF
.
right
-=
ox
;
oy
=
ox
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
bottom
-=
oy
;
}
if
(!
isInsideY
(
mFrameRectF
.
bottom
))
{
oy
=
mFrameRectF
.
bottom
-
mImageRectF
.
bottom
;
mFrameRectF
.
bottom
-=
oy
;
ox
=
oy
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
right
-=
ox
;
}
}
}
private
void
moveHandleLeftTop
(
float
diffX
,
float
diffY
)
{
if
(
mCropMode
==
CropModeEnum
.
FREE
)
{
mFrameRectF
.
left
+=
diffX
;
mFrameRectF
.
top
+=
diffY
;
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
left
-=
offsetX
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
top
-=
offsetY
;
}
checkScaleBounds
();
}
else
{
float
dx
=
diffX
;
float
dy
=
diffX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
left
+=
dx
;
mFrameRectF
.
top
+=
dy
;
// 控制缩放边界
if
(
isWidthTooSmall
())
{
float
offsetX
=
mFrameMinSize
-
mFrameRectF
.
width
();
mFrameRectF
.
left
-=
offsetX
;
float
offsetY
=
offsetX
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
top
-=
offsetY
;
}
if
(
isHeightTooSmall
())
{
float
offsetY
=
mFrameMinSize
-
mFrameRectF
.
height
();
mFrameRectF
.
top
-=
offsetY
;
float
offsetX
=
offsetY
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
left
-=
offsetX
;
}
//限制不能让裁剪框超出图片边界
float
ox
,
oy
;
if
(!
isInsideX
(
mFrameRectF
.
left
))
{
ox
=
mImageRectF
.
left
-
mFrameRectF
.
left
;
mFrameRectF
.
left
+=
ox
;
oy
=
ox
*
getRatioY
()
/
getRatioX
();
mFrameRectF
.
top
+=
oy
;
}
if
(!
isInsideY
(
mFrameRectF
.
top
))
{
oy
=
mImageRectF
.
top
-
mFrameRectF
.
top
;
mFrameRectF
.
top
+=
oy
;
ox
=
oy
*
getRatioX
()
/
getRatioY
();
mFrameRectF
.
left
+=
ox
;
}
}
}
/**
* 检查缩放边界
*/
private
void
checkScaleBounds
()
{
float
lDiff
=
mFrameRectF
.
left
-
mImageRectF
.
left
;
float
rDiff
=
mFrameRectF
.
right
-
mImageRectF
.
right
;
float
tDiff
=
mFrameRectF
.
top
-
mImageRectF
.
top
;
float
bDiff
=
mFrameRectF
.
bottom
-
mImageRectF
.
bottom
;
if
(
lDiff
<
0
)
{
mFrameRectF
.
left
-=
lDiff
;
}
if
(
rDiff
>
0
)
{
mFrameRectF
.
right
-=
rDiff
;
}
if
(
tDiff
<
0
)
{
mFrameRectF
.
top
-=
tDiff
;
}
if
(
bDiff
>
0
)
{
mFrameRectF
.
bottom
-=
bDiff
;
}
}
/**
* x 是否在图片内
*
* @param x
* @return
*/
private
boolean
isInsideX
(
float
x
)
{
return
mImageRectF
.
left
<=
x
&&
mImageRectF
.
right
>=
x
;
}
private
boolean
isInsideY
(
float
y
)
{
return
mImageRectF
.
top
<=
y
&&
mImageRectF
.
bottom
>=
y
;
}
private
boolean
isHeightTooSmall
()
{
return
mFrameRectF
.
height
()
<
mFrameMinSize
;
}
private
boolean
isWidthTooSmall
()
{
return
mFrameRectF
.
width
()
<
mFrameMinSize
;
}
private
void
moveFrame
(
float
x
,
float
y
)
{
// 1.先平移
mFrameRectF
.
left
+=
x
;
mFrameRectF
.
right
+=
x
;
mFrameRectF
.
top
+=
y
;
mFrameRectF
.
bottom
+=
y
;
// 2.判断有没有超出界外,如果超出则后退
handleMoveBounds
();
}
/**
* 控制不超出界外
*/
private
void
handleMoveBounds
()
{
float
diff
=
mFrameRectF
.
left
-
mImageRectF
.
left
;
if
(
diff
<
0
)
{
mFrameRectF
.
left
-=
diff
;
mFrameRectF
.
right
-=
diff
;
}
diff
=
mFrameRectF
.
right
-
mImageRectF
.
right
;
if
(
diff
>
0
)
{
mFrameRectF
.
left
-=
diff
;
mFrameRectF
.
right
-=
diff
;
}
diff
=
mFrameRectF
.
top
-
mImageRectF
.
top
;
if
(
diff
<
0
)
{
mFrameRectF
.
top
-=
diff
;
mFrameRectF
.
bottom
-=
diff
;
}
diff
=
mFrameRectF
.
bottom
-
mImageRectF
.
bottom
;
if
(
diff
>
0
)
{
mFrameRectF
.
top
-=
diff
;
mFrameRectF
.
bottom
-=
diff
;
}
}
private
void
onActionDown
(
MotionEvent
event
)
{
invalidate
();
mLastX
=
event
.
getX
();
mLastY
=
event
.
getY
();
handleTouchArea
(
mLastX
,
mLastY
);
}
/**
* <Strong>控制指导线或者边框线的显示</Strong>
* <p></p>
* 处理触摸的边界来控制指导线或者边框线的显示,共5种,四个角和里面的中心部分
*
* @param x
* @param y
*/
private
void
handleTouchArea
(
float
x
,
float
y
)
{
if
(
isInLeftTopCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
LEFT_TOP
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInRightTopCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
RIGHT_TOP
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInLeftBottomCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
LEFT_BOTTOM
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInRightBottomCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
RIGHT_BOTTOM
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInCenterLeftCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
CENTER_LEFT
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInCenterTopCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
CENTER_TOP
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInCenterRightCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
CENTER_RIGHT
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInCenterBottomCorner
(
x
,
y
))
{
mTouchArea
=
TouchAreaEnum
.
CENTER_BOTTOM
;
handleGuideAndHandleMode
();
return
;
}
if
(
isInFrameCenter
(
x
,
y
))
{
if
(
mGuideShowMode
==
ShowModeEnum
.
SHOW_ON_TOUCH
)
{
mShowGuide
=
true
;
}
mTouchArea
=
TouchAreaEnum
.
CENTER
;
return
;
}
// 默认情况
mTouchArea
=
TouchAreaEnum
.
OUT_OF_BOUNDS
;
}
private
boolean
isInCenterBottomCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
(
mFrameRectF
.
left
+
mFrameRectF
.
width
()
/
2
);
float
dy
=
y
-
mFrameRectF
.
bottom
;
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInCenterRightCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
mFrameRectF
.
right
;
float
dy
=
y
-
(
mFrameRectF
.
top
+
mFrameRectF
.
height
()
/
2
);
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInCenterTopCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
(
mFrameRectF
.
left
+
mFrameRectF
.
width
()
/
2
);
float
dy
=
y
-
mFrameRectF
.
top
;
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInCenterLeftCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
mFrameRectF
.
left
;
float
dy
=
y
-
(
mFrameRectF
.
top
+
mFrameRectF
.
height
()
/
2
);
return
isInsideBound
(
dx
,
dy
);
}
/**
* 处理指导线和四角线的显示模式
*/
private
void
handleGuideAndHandleMode
()
{
if
(
mHandleShowMode
==
ShowModeEnum
.
SHOW_ON_TOUCH
)
{
mShowHandle
=
true
;
}
if
(
mGuideShowMode
==
ShowModeEnum
.
SHOW_ON_TOUCH
)
{
mShowGuide
=
true
;
}
}
private
boolean
isInLeftTopCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
mFrameRectF
.
left
;
float
dy
=
y
-
mFrameRectF
.
top
;
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInRightTopCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
mFrameRectF
.
right
;
float
dy
=
y
-
mFrameRectF
.
top
;
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInLeftBottomCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
mFrameRectF
.
left
;
float
dy
=
y
-
mFrameRectF
.
bottom
;
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInRightBottomCorner
(
float
x
,
float
y
)
{
float
dx
=
x
-
mFrameRectF
.
right
;
float
dy
=
y
-
mFrameRectF
.
bottom
;
return
isInsideBound
(
dx
,
dy
);
}
private
boolean
isInsideBound
(
float
dx
,
float
dy
)
{
float
d
=
(
float
)
(
Math
.
pow
(
dx
,
2
)
+
Math
.
pow
(
dy
,
2
));
return
(
Math
.
pow
(
mHandleSize
+
mTouchPadding
,
2
))
>=
d
;
}
private
boolean
isInFrameCenter
(
float
x
,
float
y
)
{
if
(
mFrameRectF
.
left
<=
x
&&
mFrameRectF
.
right
>=
x
)
{
if
(
mFrameRectF
.
top
<=
y
&&
mFrameRectF
.
bottom
>=
y
)
{
mTouchArea
=
TouchAreaEnum
.
CENTER
;
return
true
;
}
}
return
false
;
}
/**
* 限制范围
*
* @param val
* @param min
* @param max
* @param defaultVal
* @return
*/
private
float
constrain
(
float
val
,
float
min
,
float
max
,
float
defaultVal
)
{
if
(
val
<
min
||
val
>
max
)
return
defaultVal
;
return
val
;
}
/**
* 获取当前View显示的的bitmap
*
* @return
*/
private
Bitmap
getBitmap
()
{
Bitmap
bm
=
null
;
Drawable
d
=
getDrawable
();
if
(
d
instanceof
BitmapDrawable
){
bm
=
((
BitmapDrawable
)
d
).
getBitmap
();
}
if
(
bm
!=
null
&&
bm
.
isRecycled
()){
return
null
;
}
return
bm
;
}
/**
* 设置预设裁剪坐标
* @param presetCropCoordinate
*/
public
void
setPresetCropCoordinate
(
Rect
presetCropCoordinate
)
{
this
.
presetCropCoordinate
=
presetCropCoordinate
;
initialCropRectCoordinate
();
invalidate
();
}
public
void
initialCropRectCoordinate
(){
if
(
presetCropCoordinate
==
null
){
return
;
}
int
l
=
presetCropCoordinate
.
left
;
int
t
=
presetCropCoordinate
.
top
;
int
w
=
presetCropCoordinate
.
right
-
l
;
int
h
=
presetCropCoordinate
.
bottom
-
t
;
Bitmap
source
=
getBitmap
();
if
(
source
==
null
){
return
;
}
// Bitmap rotated = getRotatedBitmap(source);
// cropRect = calcCropRect(source.getWidth(), source.getHeight());
float
rotatedWidth
=
getRotatedWidth
(
mImgAngle
,
source
.
getWidth
(),
source
.
getHeight
());
float
rotatedHeight
=
getRotatedHeight
(
mImgAngle
,
source
.
getWidth
(),
source
.
getHeight
());
float
scaleForOriginal
=
rotatedWidth
/
mImageRectF
.
width
();
float
scaleForOriginalHeight
=
rotatedHeight
/
mImageRectF
.
height
();
float
offsetX
=
mImageRectF
.
left
*
scaleForOriginal
;
float
offsetY
=
mImageRectF
.
top
*
scaleForOriginalHeight
;
int
originalLeft
=
Math
.
round
((
l
+
offsetX
)
/
scaleForOriginal
);
int
originalTop
=
Math
.
round
((
t
+
offsetY
)
/
scaleForOriginalHeight
);
int
originalRight
=
Math
.
round
((
w
+
offsetX
)
/
scaleForOriginal
)
+
originalLeft
;
int
originalBottom
=
Math
.
round
((
h
+
offsetY
)
/
scaleForOriginalHeight
)
+
originalTop
;
mFrameRectF
.
left
=
Math
.
max
(
originalLeft
,
0
);
mFrameRectF
.
top
=
Math
.
max
(
originalTop
,
0
);
mFrameRectF
.
right
=
Math
.
min
(
originalRight
,
mImageRectF
.
right
);
mFrameRectF
.
bottom
=
Math
.
min
(
originalBottom
,
mImageRectF
.
bottom
);
}
/**
* 对图片进行布局
*
* @param viewWidth
* @param viewHeight
*/
public
void
doLayout
(
int
viewWidth
,
int
viewHeight
)
{
if
(
viewHeight
==
0
||
viewWidth
==
0
)
{
viewHeight
=
mViewHeight
;
viewWidth
=
mViewWidth
;
}
if
(
viewHeight
==
0
||
viewWidth
==
0
){
return
;
}
PointF
pointF
=
new
PointF
(
getPaddingLeft
()
+
viewWidth
*
0.5f
,
getPaddingTop
()
+
viewHeight
*
0.5f
);
setCenter
(
pointF
);
setScale
(
calcScale
(
viewWidth
,
viewHeight
,
mImgAngle
));
setMatrix
();
RectF
rectF
=
new
RectF
(
0
,
0
,
mImgWidth
,
mImgHeight
);
mImageRectF
=
calcImageRect
(
rectF
,
mMatrix
);
mFrameRectF
=
calculateFrameRect
(
mImageRectF
);
// 加载预设的裁剪坐标
if
(
presetCropCoordinate
!=
null
){
initialCropRectCoordinate
();
}
mIsInitialized
=
true
;
invalidate
();
}
/**
* 重设矩阵,平移缩放旋转操作
*/
private
void
setMatrix
()
{
mMatrix
.
reset
();
mMatrix
.
setTranslate
(
mCenter
.
x
-
mImgWidth
*
0.5f
,
mCenter
.
y
-
mImgHeight
*
0.5f
);
mMatrix
.
postScale
(
mCropScale
,
mCropScale
,
mCenter
.
x
,
mCenter
.
y
);
if
(
mRotateSwitch
)
{
mMatrix
.
postRotate
(
mImgAngle
,
mCenter
.
x
,
mCenter
.
y
);
}
}
private
RectF
calculateFrameRect
(
RectF
imageRect
)
{
Log
.
d
(
"image"
,
"width:"
+
imageRect
.
width
()
+
"height"
+
imageRect
.
height
());
float
frameW
=
getRatioX
(
imageRect
.
width
());
float
frameH
=
getRatioY
(
imageRect
.
height
());
float
frameRatio
=
frameW
/
frameH
;
float
imgRatio
=
imageRect
.
width
()
/
imageRect
.
height
();
float
l
=
imageRect
.
left
,
t
=
imageRect
.
top
,
r
=
imageRect
.
right
,
b
=
imageRect
.
bottom
;
if
(
frameRatio
>=
imgRatio
)
{
//宽比长比例大于img图宽高比的情况
l
=
imageRect
.
left
;
r
=
imageRect
.
right
;
//图的中点
float
hy
=
(
imageRect
.
top
+
imageRect
.
bottom
)
*
0.5f
;
//中点到上下顶点坐标的距离
float
hh
=
(
imageRect
.
width
()
/
frameRatio
)
*
0.5f
;
t
=
hy
-
hh
;
b
=
hy
+
hh
;
}
else
if
(
frameRatio
<
imgRatio
)
{
//宽比长比例大于img图宽高比的情况
t
=
imageRect
.
top
;
b
=
imageRect
.
bottom
;
float
hx
=
(
imageRect
.
left
+
imageRect
.
right
)
*
0.5f
;
float
hw
=
imageRect
.
height
()
*
frameRatio
*
0.5f
;
l
=
hx
-
hw
;
r
=
hx
+
hw
;
}
//裁剪框宽度
float
w
=
r
-
l
;
//高度
float
h
=
b
-
t
;
//中心点
float
cx
=
l
+
w
/
2
;
float
cy
=
t
+
h
/
2
;
//放大后的裁剪框的宽高
float
sw
=
w
*
mInitialFrameScale
;
float
sh
=
h
*
mInitialFrameScale
;
return
new
RectF
(
cx
-
sw
/
2
,
cy
-
sh
/
2
,
cx
+
sw
/
2
,
cy
+
sh
/
2
);
}
private
float
getRatioX
(
float
w
)
{
switch
(
mCropMode
)
{
case
FIT_IMAGE:
return
mImageRectF
.
width
();
case
FREE:
return
w
;
case
RATIO_2_3:
return
2
;
case
RATIO_3_2:
return
3
;
case
RATIO_4_3:
return
4
;
case
RATIO_3_4:
return
3
;
case
RATIO_16_9:
return
16
;
case
RATIO_9_16:
return
9
;
case
SQUARE:
return
1
;
default
:
return
w
;
}
}
private
float
getRatioY
(
float
h
)
{
switch
(
mCropMode
)
{
case
FIT_IMAGE:
return
mImageRectF
.
height
();
case
FREE:
return
h
;
case
RATIO_2_3:
return
3
;
case
RATIO_3_2:
return
2
;
case
RATIO_4_3:
return
3
;
case
RATIO_3_4:
return
4
;
case
RATIO_16_9:
return
9
;
case
RATIO_9_16:
return
16
;
case
SQUARE:
return
1
;
default
:
return
h
;
}
}
private
float
getRatioX
()
{
switch
(
mCropMode
)
{
case
FIT_IMAGE:
return
mImageRectF
.
width
();
case
RATIO_2_3:
return
2
;
case
RATIO_3_2:
return
3
;
case
RATIO_4_3:
return
4
;
case
RATIO_3_4:
return
3
;
case
RATIO_16_9:
return
16
;
case
RATIO_9_16:
return
9
;
case
SQUARE:
return
1
;
default
:
return
1
;
}
}
private
float
getRatioY
()
{
switch
(
mCropMode
)
{
case
FIT_IMAGE:
return
mImageRectF
.
height
();
case
RATIO_2_3:
return
3
;
case
RATIO_3_2:
return
2
;
case
RATIO_4_3:
return
3
;
case
RATIO_3_4:
return
4
;
case
RATIO_16_9:
return
9
;
case
RATIO_9_16:
return
16
;
case
SQUARE:
return
1
;
default
:
return
1
;
}
}
/**
* 将Matrix映射到rect
*
* @param rect
* @param matrix
* @return
*/
private
RectF
calcImageRect
(
RectF
rect
,
Matrix
matrix
)
{
RectF
applied
=
new
RectF
();
matrix
.
mapRect
(
applied
,
rect
);
return
applied
;
}
private
float
calcScale
(
int
viewWidth
,
int
viewHeight
,
float
angle
)
{
mImgWidth
=
getDrawable
().
getIntrinsicWidth
();
mImgHeight
=
getDrawable
().
getIntrinsicHeight
();
if
(
mImgWidth
<=
0
)
{
mImgWidth
=
viewWidth
;
}
if
(
mImgHeight
<=
0
)
{
mImgHeight
=
viewHeight
;
}
float
viewRatio
=
(
float
)
viewWidth
/
(
float
)
viewHeight
;
float
imgRatio
=
getRotatedWidth
(
angle
)
/
getRotatedHeight
(
angle
);
float
scale
=
1.0f
;
if
(
imgRatio
>=
viewRatio
)
{
scale
=
viewWidth
/
getRotatedWidth
(
angle
);
}
else
if
(
imgRatio
<
viewRatio
)
{
scale
=
viewHeight
/
getRotatedHeight
(
angle
);
}
return
scale
;
}
private
float
getRotatedWidth
(
float
angle
)
{
return
getRotatedWidth
(
angle
,
mImgWidth
,
mImgHeight
);
}
private
float
getRotatedWidth
(
float
angle
,
float
width
,
float
height
)
{
return
angle
%
180
==
0
?
width
:
height
;
}
private
float
getRotatedHeight
(
float
angle
)
{
return
getRotatedHeight
(
angle
,
mImgWidth
,
mImgHeight
);
}
private
float
getRotatedHeight
(
float
angle
,
float
width
,
float
height
)
{
return
angle
%
180
==
0
?
height
:
width
;
}
/**
* 保存中心矩阵
*
* @param center
*/
private
void
setCenter
(
PointF
center
)
{
mCenter
=
center
;
}
/**
* 保存比例
*
* @param scale
*/
private
void
setScale
(
float
scale
)
{
mCropScale
=
scale
;
}
/**
* 旋转图片
*
* @param degrees
*/
public
void
rotateImage
(
RotateDegreesEnum
degrees
)
{
// 标记有旋转操作
mIsRotated
=
true
;
if
(
mIsReverseY
)
{
degrees
=
reverseDegrees
(
degrees
);
}
rotateImage
(
degrees
,
mAnimationDurationMillis
);
}
/**
* 将翻转方向取反
*
* @param degrees
* @return
*/
private
RotateDegreesEnum
reverseDegrees
(
RotateDegreesEnum
degrees
)
{
// 处理翻转后的旋转方向改变问题
switch
(
degrees
)
{
case
ROTATE_90D:
{
degrees
=
RotateDegreesEnum
.
ROTATE_M90D
;
break
;
}
case
ROTATE_M90D:
{
degrees
=
RotateDegreesEnum
.
ROTATE_90D
;
break
;
}
case
ROTATE_180D:
{
degrees
=
RotateDegreesEnum
.
ROTATE_M180D
;
break
;
}
case
ROTATE_M180D:
{
degrees
=
RotateDegreesEnum
.
ROTATE_180D
;
break
;
}
case
ROTATE_270D:
{
degrees
=
RotateDegreesEnum
.
ROTATE_M270D
;
break
;
}
case
ROTATE_M270D:
{
degrees
=
RotateDegreesEnum
.
ROTATE_270D
;
break
;
}
}
return
degrees
;
}
/**
* 旋转图片
*
* @param degrees
* @param durationMillis 需要调整的动画时间
*/
private
void
rotateImage
(
RotateDegreesEnum
degrees
,
int
durationMillis
)
{
if
(
mIsRotating
)
{
mValueAnimator
.
cancel
();
}
final
float
currentAngle
=
mImgAngle
;
final
float
newAngle
=
(
mImgAngle
+
degrees
.
getValue
());
final
float
angleDiff
=
newAngle
-
currentAngle
;
final
float
currentScale
=
mCropScale
;
final
float
newScale
=
calcScale
(
mViewWidth
,
mViewHeight
,
newAngle
);
if
(
mIsAnimationEnabled
)
{
final
float
scaleDiff
=
newScale
-
currentScale
;
mValueAnimator
.
addListener
(
new
AnimatorListenerAdapter
()
{
@Override
public
void
onAnimationEnd
(
Animator
animation
)
{
super
.
onAnimationEnd
(
animation
);
mImgAngle
=
newAngle
%
360
;
mCropScale
=
newScale
;
doLayout
(
mViewWidth
,
mViewHeight
);
mIsRotating
=
false
;
}
@Override
public
void
onAnimationStart
(
Animator
animation
)
{
super
.
onAnimationStart
(
animation
);
mIsRotating
=
true
;
}
});
mValueAnimator
.
addUpdateListener
(
new
ValueAnimator
.
AnimatorUpdateListener
()
{
@Override
public
void
onAnimationUpdate
(
ValueAnimator
animation
)
{
float
scale
=
(
float
)
animation
.
getAnimatedValue
();
mImgAngle
=
currentAngle
+
angleDiff
*
scale
;
mCropScale
=
currentScale
+
scaleDiff
*
scale
;
setMatrix
();
invalidate
();
}
});
mValueAnimator
.
setDuration
(
durationMillis
);
mValueAnimator
.
start
();
}
else
{
mImgAngle
=
newAngle
%
360
;
mCropScale
=
newScale
;
doLayout
(
mViewWidth
,
mViewHeight
);
}
}
/**
* 左右翻转
*/
public
void
reverseY
()
{
if
(!
mIsReverseY
)
{
mIsReverseY
=
true
;
}
else
{
mIsReverseY
=
false
;
}
super
.
setScaleX
(
getScaleX
()
*
-
1
f
);
}
public
boolean
isFirstSetImageBitmap
=
true
;
private
Bitmap
lastBitmap
=
null
;
@Override
public
void
setImageBitmap
(
Bitmap
bm
)
{
super
.
setImageBitmap
(
bm
);
if
(
isFirstSetImageBitmap
){
isFirstSetImageBitmap
=
false
;
invalidate
();
}
if
(
lastBitmap
!=
null
&&
!
lastBitmap
.
isRecycled
()){
// 回收上一帧
lastBitmap
.
recycle
();
}
lastBitmap
=
bm
;
}
public
Rect
getCropRect
()
{
return
cropRect
;
}
/**
* 裁剪图像返回裁剪后的bitmap
* <p>
* <Strong>NOTICE:工作在异步线程</Strong>
*
* @return
*/
public
void
cropImage
(
OnCropListener
onCropListener
)
{
addOnCropListener
(
onCropListener
);
Observable
.
create
(
new
ObservableOnSubscribe
<
Object
>()
{
@Override
public
void
subscribe
(
ObservableEmitter
<
Object
>
emitter
)
throws
Exception
{
Bitmap
cropped
=
getCroppedBitmap
();
if
(
mOnCropListener
!=
null
)
{
mOnCropListener
.
onCropFinished
(
cropped
,
cropRect
.
left
,
cropRect
.
top
,
cropRect
.
width
(),
cropRect
.
height
());
}
}})
.
subscribeOn
(
Schedulers
.
io
())
.
observeOn
(
AndroidSchedulers
.
mainThread
())
.
subscribe
(
new
Observer
<
Object
>()
{
@Override
public
void
onSubscribe
(
Disposable
d
)
{
}
@Override
public
void
onNext
(
Object
o
)
{
}
@Override
public
void
onError
(
Throwable
e
)
{
}
@Override
public
void
onComplete
()
{
}
});
}
/**
* @return
*/
public
Bitmap
getCroppedBitmap
()
{
Bitmap
source
=
getBitmap
();
if
(
source
==
null
)
{
return
null
;
}
Bitmap
rotated
=
getRotatedBitmap
(
source
);
cropRect
=
calcCropRect
(
source
.
getWidth
(),
source
.
getHeight
());
Bitmap
cropped
=
Bitmap
.
createBitmap
(
rotated
,
cropRect
.
left
,
cropRect
.
top
,
cropRect
.
width
(),
cropRect
.
height
(),
null
,
false
);
if
(
rotated
!=
cropped
&&
rotated
!=
source
)
{
rotated
.
recycle
();
}
if
(
mIsReverseY
)
{
// 如果翻转了照片,那么对照片进行翻转处理
cropped
=
reverseImg
(
cropped
,
-
1
,
1
);
}
return
cropped
;
}
private
Rect
calcCropRect
(
int
originalImageWidth
,
int
originalImageHeight
)
{
float
rotatedWidth
=
getRotatedWidth
(
mImgAngle
,
originalImageWidth
,
originalImageHeight
);
float
rotatedHeight
=
getRotatedHeight
(
mImgAngle
,
originalImageWidth
,
originalImageHeight
);
float
scaleForOriginal
=
rotatedWidth
/
mImageRectF
.
width
();
float
offsetX
=
mImageRectF
.
left
*
scaleForOriginal
;
float
offsetY
=
mImageRectF
.
top
*
scaleForOriginal
;
int
left
=
Math
.
round
(
mFrameRectF
.
left
*
scaleForOriginal
-
offsetX
);
int
top
=
Math
.
round
(
mFrameRectF
.
top
*
scaleForOriginal
-
offsetY
);
int
right
=
Math
.
round
(
mFrameRectF
.
right
*
scaleForOriginal
-
offsetX
);
int
bottom
=
Math
.
round
(
mFrameRectF
.
bottom
*
scaleForOriginal
-
offsetY
);
int
imageW
=
Math
.
round
(
rotatedWidth
);
int
imageH
=
Math
.
round
(
rotatedHeight
);
return
new
Rect
(
Math
.
max
(
left
,
0
),
Math
.
max
(
top
,
0
),
Math
.
min
(
right
,
imageW
),
Math
.
min
(
bottom
,
imageH
));
}
/**
* 得到旋转后的Bitmap
*
* @param bitmap
* @return
*/
private
Bitmap
getRotatedBitmap
(
Bitmap
bitmap
)
{
Matrix
rotateMatrix
=
new
Matrix
();
rotateMatrix
.
postRotate
(
mImgAngle
,
bitmap
.
getWidth
()
/
2
,
bitmap
.
getHeight
()
/
2
);
return
Bitmap
.
createBitmap
(
bitmap
,
0
,
0
,
bitmap
.
getWidth
(),
bitmap
.
getHeight
(),
rotateMatrix
,
true
);
}
private
void
addOnCropListener
(
OnCropListener
onCropListener
)
{
mOnCropListener
=
onCropListener
;
}
@UiThread
public
void
setCropMode
(
CropModeEnum
mode
)
{
setCropMode
(
mode
,
mAnimationDurationMillis
);
}
/**
* 设置裁剪层的开关
*
* @param enabled
*/
@UiThread
public
void
setCropEnabled
(
boolean
enabled
)
{
mIsCropEnabled
=
enabled
;
invalidate
();
}
private
void
setCropMode
(
CropModeEnum
mode
,
int
durationMillis
)
{
this
.
mCropMode
=
mode
;
recalculateFrameRect
(
durationMillis
);
}
private
void
recalculateFrameRect
(
int
durationMillis
)
{
if
(
mImageRectF
==
null
)
{
return
;
}
if
(
mIsAnimating
)
{
mFrameValueAnimator
.
cancel
();
}
final
RectF
currentRect
=
new
RectF
(
mFrameRectF
);
final
RectF
newRect
=
calculateFrameRect
(
mImageRectF
);
final
float
diffL
=
newRect
.
left
-
currentRect
.
left
;
final
float
diffT
=
newRect
.
top
-
currentRect
.
top
;
final
float
diffR
=
newRect
.
right
-
currentRect
.
right
;
final
float
diffB
=
newRect
.
bottom
-
currentRect
.
bottom
;
if
(
mIsAnimationEnabled
)
{
mFrameValueAnimator
.
addListener
(
new
AnimatorListenerAdapter
()
{
@Override
public
void
onAnimationEnd
(
Animator
animation
)
{
super
.
onAnimationEnd
(
animation
);
mFrameRectF
=
newRect
;
invalidate
();
mIsAnimating
=
false
;
}
@Override
public
void
onAnimationStart
(
Animator
animation
)
{
super
.
onAnimationStart
(
animation
);
mIsAnimating
=
true
;
}
});
mFrameValueAnimator
.
addUpdateListener
(
new
ValueAnimator
.
AnimatorUpdateListener
()
{
@Override
public
void
onAnimationUpdate
(
ValueAnimator
animation
)
{
float
scale
=
(
float
)
animation
.
getAnimatedValue
();
// 裁剪框放大的动画RectF
mFrameRectF
=
new
RectF
(
currentRect
.
left
+
diffL
*
scale
,
currentRect
.
top
+
diffT
*
scale
,
currentRect
.
right
+
diffR
*
scale
,
currentRect
.
bottom
+
diffB
*
scale
);
invalidate
();
}
});
mFrameValueAnimator
.
setDuration
(
durationMillis
);
mFrameValueAnimator
.
start
();
}
else
{
mFrameRectF
=
calculateFrameRect
(
mImageRectF
);
invalidate
();
}
}
@Override
protected
void
onDetachedFromWindow
()
{
super
.
onDetachedFromWindow
();
if
(
mValueAnimator
!=
null
)
{
mValueAnimator
.
cancel
();
mValueAnimator
=
null
;
}
if
(
mFrameValueAnimator
!=
null
)
{
mFrameValueAnimator
.
cancel
();
mFrameValueAnimator
=
null
;
}
if
(
mOnCropListener
!=
null
)
{
mOnCropListener
=
null
;
}
}
/**
* 用于确认后显示预览图
*
* @param bitmap
*/
public
void
showBitmap
(
Bitmap
bitmap
)
{
if
(
mIsRotated
)
{
mRotateSwitch
=
false
;
}
this
.
setImageBitmap
(
bitmap
);
// 排除翻转的左右翻转问题
if
(
mIsReverseY
)
{
// 恢复CropView的ScaleX值
this
.
setScaleX
(
this
.
getScaleX
()
*
-
1
f
);
}
}
/**
* 翻转照片到相应的尺寸
*
* @param bm
* @param x -1时为水平翻转
* @param y -1时为垂直翻转
* @return
*/
public
Bitmap
reverseImg
(
Bitmap
bm
,
int
x
,
int
y
)
{
int
width
=
bm
.
getWidth
();
int
height
=
bm
.
getHeight
();
Matrix
matrix
=
new
Matrix
();
matrix
.
postScale
(
x
,
1
);
matrix
.
postScale
(
1
,
y
);
return
Bitmap
.
createBitmap
(
bm
,
0
,
0
,
width
,
height
,
matrix
,
true
);
}
/**
* 手指点击的区域枚举类
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 19:00
*/
public
enum
TouchAreaEnum
{
CENTER
,
LEFT_TOP
,
RIGHT_TOP
,
LEFT_BOTTOM
,
RIGHT_BOTTOM
,
OUT_OF_BOUNDS
,
CENTER_LEFT
,
CENTER_TOP
,
CENTER_RIGHT
,
CENTER_BOTTOM
}
/**
* 裁剪框的比例模式
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 19:01
*/
public
enum
CropModeEnum
{
FIT_IMAGE
(
0
),
RATIO_2_3
(
1
),
RATIO_3_2
(
2
),
RATIO_4_3
(
3
),
RATIO_3_4
(
4
),
SQUARE
(
5
),
RATIO_16_9
(
6
),
RATIO_9_16
(
7
),
FREE
(
8
);
private
final
int
ID
;
CropModeEnum
(
int
id
)
{
ID
=
id
;
}
public
int
getID
()
{
return
ID
;
}
}
/**
* 旋转角度的枚举类
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 19:07
*/
public
enum
RotateDegreesEnum
{
ROTATE_90D
(
90
),
ROTATE_180D
(
180
),
ROTATE_270D
(
270
),
ROTATE_M90D
(-
90
),
ROTATE_M180D
(-
180
),
ROTATE_M270D
(-
270
),
ROTATE_0D
(
0
);
private
final
int
VALUE
;
RotateDegreesEnum
(
final
int
value
)
{
this
.
VALUE
=
value
;
}
public
int
getValue
()
{
return
VALUE
;
}
}
/**
* 展示模式,控制裁剪框显示的时机
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 20:44
*/
public
enum
ShowModeEnum
{
SHOW_ALWAYS
(
1
),
SHOW_ON_TOUCH
(
2
),
NOT_SHOW
(
3
);
private
final
int
ID
;
ShowModeEnum
(
final
int
id
)
{
this
.
ID
=
id
;
}
public
int
getId
()
{
return
ID
;
}
}
public
interface
OnCropListener
{
void
onCropFinished
(
Bitmap
bitmap
,
int
left
,
int
top
,
int
width
,
int
height
);
}
@Override
protected
void
onRestoreInstanceState
(
Parcelable
state
)
{
super
.
onRestoreInstanceState
(
state
);
SavedState
ss
=
(
SavedState
)
state
;
this
.
mCropMode
=
ss
.
mCropMode
;
this
.
mImgAngle
=
ss
.
mImgAngle
;
this
.
mImgWidth
=
ss
.
mImgWidth
;
this
.
mImgHeight
=
ss
.
mImgHeight
;
this
.
mHandleSize
=
ss
.
mHandleSize
;
this
.
mHandleWidth
=
ss
.
mHandleWidth
;
this
.
mTouchPadding
=
ss
.
mTouchPadding
;
this
.
mFrameMinSize
=
ss
.
mFrameMinSize
;
this
.
mFrameStrokeWeight
=
ss
.
mFrameStrokeWeight
;
this
.
mGuideStrokeWeight
=
ss
.
mGuideStrokeWeight
;
this
.
mBackgroundColor
=
ss
.
mBackgroundColor
;
this
.
mOverlayColor
=
ss
.
mOverlayColor
;
this
.
mFrameColor
=
ss
.
mFrameColor
;
this
.
mHandleColor
=
ss
.
mHandleColor
;
this
.
mGuideColor
=
ss
.
mGuideColor
;
this
.
mCropMode
=
ss
.
mCropMode
;
this
.
mGuideShowMode
=
ss
.
mGuideShowMode
;
this
.
mHandleShowMode
=
ss
.
mHandleShowMode
;
this
.
mInitialFrameScale
=
ss
.
mInitialFrameScale
;
this
.
mIsInitialized
=
ss
.
mIsInitialized
;
this
.
mIsCropEnabled
=
ss
.
mIsCropEnabled
;
this
.
mShowGuide
=
ss
.
mShowGuide
;
this
.
mShowHandle
=
ss
.
mShowHandle
;
this
.
mIsRotating
=
ss
.
mIsRotating
;
this
.
mIsAnimating
=
ss
.
mIsAnimating
;
this
.
mIsReverseY
=
ss
.
mIsReverseY
;
this
.
mIsRotated
=
ss
.
mIsRotated
;
this
.
mRotateSwitch
=
ss
.
mRotateSwitch
;
}
@Override
protected
Parcelable
onSaveInstanceState
()
{
Parcelable
superParcelable
=
super
.
onSaveInstanceState
();
SavedState
ss
=
new
SavedState
(
superParcelable
);
ss
.
mCropMode
=
this
.
mCropMode
;
ss
.
mImgAngle
=
this
.
mImgAngle
;
ss
.
mImgWidth
=
this
.
mImgWidth
;
ss
.
mImgHeight
=
this
.
mImgHeight
;
ss
.
mHandleSize
=
this
.
mHandleSize
;
ss
.
mHandleWidth
=
this
.
mHandleWidth
;
ss
.
mTouchPadding
=
this
.
mTouchPadding
;
ss
.
mFrameMinSize
=
this
.
mFrameMinSize
;
ss
.
mFrameStrokeWeight
=
this
.
mFrameStrokeWeight
;
ss
.
mGuideStrokeWeight
=
this
.
mGuideStrokeWeight
;
ss
.
mBackgroundColor
=
this
.
mBackgroundColor
;
ss
.
mOverlayColor
=
this
.
mOverlayColor
;
ss
.
mFrameColor
=
this
.
mFrameColor
;
ss
.
mHandleColor
=
this
.
mHandleColor
;
ss
.
mGuideColor
=
this
.
mGuideColor
;
ss
.
mCropMode
=
this
.
mCropMode
;
ss
.
mGuideShowMode
=
this
.
mGuideShowMode
;
ss
.
mHandleShowMode
=
this
.
mHandleShowMode
;
ss
.
mInitialFrameScale
=
this
.
mInitialFrameScale
;
ss
.
mIsInitialized
=
this
.
mIsInitialized
;
ss
.
mIsCropEnabled
=
this
.
mIsCropEnabled
;
ss
.
mShowGuide
=
this
.
mShowGuide
;
ss
.
mShowHandle
=
this
.
mShowHandle
;
ss
.
mIsRotating
=
this
.
mIsRotating
;
ss
.
mIsAnimating
=
this
.
mIsAnimating
;
ss
.
mIsReverseY
=
this
.
mIsReverseY
;
ss
.
mIsRotated
=
this
.
mIsRotated
;
ss
.
mRotateSwitch
=
this
.
mRotateSwitch
;
return
ss
;
}
public
static
class
SavedState
extends
BaseSavedState
{
// 放大比例
private
float
mCropScale
=
1.0f
;
// 旋转角度
private
float
mImgAngle
=
0.0f
;
// 图片宽度
private
float
mImgWidth
=
0.0f
;
// 图片高度
private
float
mImgHeight
=
0.0f
;
// 四角线的长度值
private
int
mHandleSize
;
// 四角的线宽度
private
int
mHandleWidth
;
// 触摸可处理的宽度值
private
int
mTouchPadding
=
0
;
// 裁剪框最小值
private
int
mFrameMinSize
;
// 裁剪外框线框宽度
private
float
mFrameStrokeWeight
=
2
f
;
// 裁剪指导线宽度
private
float
mGuideStrokeWeight
=
2
f
;
// 背景的颜色
private
int
mBackgroundColor
;
// 半透明框的颜色
private
int
mOverlayColor
;
// 外框的颜色
private
int
mFrameColor
;
// 四边角点的颜色
private
int
mHandleColor
;
// 指导线颜色
private
int
mGuideColor
;
// 裁剪模式
private
CropModeEnum
mCropMode
;
// 指导线的显示模式
private
ShowModeEnum
mGuideShowMode
;
// 处理四个角的显示模式
private
ShowModeEnum
mHandleShowMode
;
// 初始化的比例大小0.01到1.0,默认情况为0.75
private
float
mInitialFrameScale
;
// 是否初始化
private
boolean
mIsInitialized
;
// 是否开始裁剪
private
boolean
mIsCropEnabled
=
true
;
// 是否显示指导线
private
boolean
mShowGuide
=
true
;
// 是否显示处理线
private
boolean
mShowHandle
=
true
;
private
boolean
mIsRotating
=
false
;
private
boolean
mIsAnimating
=
false
;
// 是否已经翻转了
private
boolean
mIsReverseY
=
false
;
// 是否有旋转操作
private
boolean
mIsRotated
=
false
;
// 旋转操作的开关
private
boolean
mRotateSwitch
=
true
;
SavedState
(
Parcelable
superState
)
{
super
(
superState
);
}
private
SavedState
(
Parcel
source
)
{
super
(
source
);
mGuideColor
=
source
.
readInt
();
mHandleColor
=
source
.
readInt
();
mFrameColor
=
source
.
readInt
();
mOverlayColor
=
source
.
readInt
();
mBackgroundColor
=
source
.
readInt
();
mFrameMinSize
=
source
.
readInt
();
mTouchPadding
=
source
.
readInt
();
mHandleWidth
=
source
.
readInt
();
mHandleSize
=
source
.
readInt
();
mImgHeight
=
source
.
readFloat
();
mImgWidth
=
source
.
readFloat
();
mImgAngle
=
source
.
readFloat
();
mCropScale
=
source
.
readFloat
();
mFrameStrokeWeight
=
source
.
readFloat
();
mGuideStrokeWeight
=
source
.
readFloat
();
mHandleShowMode
=
(
ShowModeEnum
)
source
.
readSerializable
();
mGuideShowMode
=
(
ShowModeEnum
)
source
.
readSerializable
();
mCropMode
=
(
CropModeEnum
)
source
.
readSerializable
();
mInitialFrameScale
=
source
.
readFloat
();
mRotateSwitch
=
(
source
.
readInt
()
!=
0
);
mIsRotated
=
(
source
.
readInt
()
!=
0
);
mIsReverseY
=
(
source
.
readInt
()
!=
0
);
mIsAnimating
=
(
source
.
readInt
()
!=
0
);
mIsRotating
=
(
source
.
readInt
()
!=
0
);
mShowHandle
=
(
source
.
readInt
()
!=
0
);
mShowGuide
=
(
source
.
readInt
()
!=
0
);
mIsCropEnabled
=
(
source
.
readInt
()
!=
0
);
mIsInitialized
=
(
source
.
readInt
()
!=
0
);
}
@Override
public
void
writeToParcel
(
Parcel
out
,
int
flags
)
{
super
.
writeToParcel
(
out
,
flags
);
out
.
writeInt
(
mGuideColor
);
out
.
writeInt
(
mHandleColor
);
out
.
writeInt
(
mFrameColor
);
out
.
writeInt
(
mOverlayColor
);
out
.
writeInt
(
mBackgroundColor
);
out
.
writeInt
(
mFrameMinSize
);
out
.
writeInt
(
mTouchPadding
);
out
.
writeInt
(
mHandleWidth
);
out
.
writeInt
(
mHandleSize
);
out
.
writeFloat
(
mImgHeight
);
out
.
writeFloat
(
mImgWidth
);
out
.
writeFloat
(
mImgAngle
);
out
.
writeFloat
(
mCropScale
);
out
.
writeFloat
(
mFrameStrokeWeight
);
out
.
writeFloat
(
mGuideStrokeWeight
);
out
.
writeSerializable
(
mHandleShowMode
);
out
.
writeSerializable
(
mGuideShowMode
);
out
.
writeSerializable
(
mCropMode
);
out
.
writeFloat
(
mInitialFrameScale
);
out
.
writeInt
(
mRotateSwitch
?
1
:
0
);
out
.
writeInt
(
mIsRotated
?
1
:
0
);
out
.
writeInt
(
mIsReverseY
?
1
:
0
);
out
.
writeInt
(
mIsAnimating
?
1
:
0
);
out
.
writeInt
(
mIsRotating
?
1
:
0
);
out
.
writeInt
(
mShowHandle
?
1
:
0
);
out
.
writeInt
(
mShowGuide
?
1
:
0
);
out
.
writeInt
(
mIsCropEnabled
?
1
:
0
);
out
.
writeInt
(
mIsInitialized
?
1
:
0
);
}
public
static
final
Creator
CREATOR
=
new
Creator
()
{
@Override
public
SavedState
createFromParcel
(
Parcel
parcel
)
{
return
new
SavedState
(
parcel
);
}
@Override
public
SavedState
[]
newArray
(
int
i
)
{
return
new
SavedState
[
i
];
}
};
}
}
common/src/main/res/values/themes.xml
View file @
dfa400a3
...
...
@@ -33,4 +33,43 @@
<item
name=
"android:textColor"
>
@color/white
</item>
</style>
<!--自定义裁剪View-->
<declare-styleable
name=
"CropView"
>
<attr
name=
"img_src"
format=
"reference"
/>
<attr
name=
"crop_mode"
>
<enum
name=
"fit_image"
value=
"0"
/>
<enum
name=
"ratio_2_3"
value=
"1"
/>
<enum
name=
"ratio_3_2"
value=
"2"
/>
<enum
name=
"ratio_4_3"
value=
"3"
/>
<enum
name=
"ratio_3_4"
value=
"4"
/>
<enum
name=
"square"
value=
"5"
/>
<enum
name=
"ratio_16_9"
value=
"6"
/>
<enum
name=
"ratio_9_16"
value=
"7"
/>
<enum
name=
"free"
value=
"8"
/>
</attr>
<attr
name=
"background_color"
format=
"reference|color"
/>
<attr
name=
"overlay_color"
format=
"reference|color"
/>
<attr
name=
"framecolor"
format=
"reference|color"
/>
<attr
name=
"handle_color"
format=
"reference|color"
/>
<attr
name=
"handle_width"
format=
"dimension"
/>
<attr
name=
"handle_size"
format=
"dimension"
/>
<attr
name=
"guide_color"
format=
"reference|color"
/>
<attr
name=
"guide_show_mode"
>
<enum
name=
"show_always"
value=
"1"
/>
<enum
name=
"show_on_touch"
value=
"2"
/>
<enum
name=
"not_show"
value=
"3"
/>
</attr>
<attr
name=
"handle_show_mode"
>
<enum
name=
"show_always"
value=
"1"
/>
<enum
name=
"show_on_touch"
value=
"2"
/>
<enum
name=
"not_show"
value=
"3"
/>
</attr>
<attr
name=
"touch_padding"
format=
"dimension"
/>
<attr
name=
"min_frame_size"
format=
"dimension"
/>
<attr
name=
"frame_stroke_weight"
format=
"dimension"
/>
<attr
name=
"guide_stroke_weight"
format=
"dimension"
/>
<attr
name=
"crop_enabled"
format=
"boolean"
/>
<attr
name=
"initial_frame_scale"
format=
"float"
/>
</declare-styleable>
</resources>
\ No newline at end of file
data-local/src/main/java/com/wmdigit/data/mmkv/repository/CropLocalRepository.java
View file @
dfa400a3
package
com
.
wmdigit
.
data
.
mmkv
.
repository
;
import
com.elvishew.xlog.XLog
;
import
com.tencent.mmkv.MMKV
;
import
com.wmdigit.common.model.CropValue
;
import
com.wmdigit.data.mmkv.constant.MmkvCons
;
/**
...
...
@@ -26,8 +28,33 @@ public class CropLocalRepository {
* 获取是否裁剪
* @return
*/
public
boolean
get
IsCrop
(){
public
boolean
get
HasCropped
(){
return
MMKV
.
defaultMMKV
().
getInt
(
MmkvCons
.
MMKV_KEY_CROP_WIDTH
,
0
)
>
0
;
}
/**
* 保存裁剪坐标
* @param value
*/
public
void
saveCropValue
(
CropValue
value
){
MMKV
.
defaultMMKV
().
putInt
(
MmkvCons
.
MMKV_KEY_CROP_WIDTH
,
value
.
getWidth
());
MMKV
.
defaultMMKV
().
putInt
(
MmkvCons
.
MMKV_KEY_CROP_HEIGHT
,
value
.
getHeight
());
MMKV
.
defaultMMKV
().
putInt
(
MmkvCons
.
MMKV_KEY_CROP_X
,
value
.
getLeft
());
MMKV
.
defaultMMKV
().
putInt
(
MmkvCons
.
MMKV_KEY_CROP_Y
,
value
.
getTop
());
}
/**
* 获取裁剪坐标
* @return
*/
public
CropValue
getCropValue
(){
CropValue
value
=
new
CropValue
(
MMKV
.
defaultMMKV
().
getInt
(
MmkvCons
.
MMKV_KEY_CROP_X
,
0
),
MMKV
.
defaultMMKV
().
getInt
(
MmkvCons
.
MMKV_KEY_CROP_Y
,
0
),
MMKV
.
defaultMMKV
().
getInt
(
MmkvCons
.
MMKV_KEY_CROP_WIDTH
,
0
),
MMKV
.
defaultMMKV
().
getInt
(
MmkvCons
.
MMKV_KEY_CROP_HEIGHT
,
0
)
);
return
value
;
}
}
history.txt
View file @
dfa400a3
...
...
@@ -4,5 +4,5 @@ v1.0.1 2024/08/01 1.增加camerax
v1.0.2 2024/08/06 1.增加系统信息页
2.增加设置-注册页
3.对接注册、找回接口
4.
todo
增加秤盘裁剪页
4.增加秤盘裁剪页
5.todo 增加AIDL服务
\ No newline at end of file
module-setting/src/main/java/com/wmdigit/setting/fragment/CameraCropFragment.java
View file @
dfa400a3
package
com
.
wmdigit
.
setting
.
fragment
;
import
com.elvishew.xlog.XLog
;
import
com.wmdigit.camera.CameraxController
;
import
com.wmdigit.common.base.mvvm.BaseMvvmFragment
;
import
com.wmdigit.setting.R
;
import
com.wmdigit.setting.databinding.FragmentCameraCropBinding
;
...
...
@@ -12,6 +14,7 @@ import com.wmdigit.setting.viewmodel.CameraCropViewModel;
*/
public
class
CameraCropFragment
extends
BaseMvvmFragment
<
CameraCropViewModel
,
FragmentCameraCropBinding
>
{
private
boolean
isCameraNeedRelease
=
false
;
@Override
protected
int
getLayoutId
()
{
...
...
@@ -26,11 +29,48 @@ public class CameraCropFragment extends BaseMvvmFragment<CameraCropViewModel, Fr
@Override
protected
void
initData
()
{
mDataBinding
.
setViewModel
(
mViewModel
);
// 检查Camera是否开启
if
(!
CameraxController
.
getInstance
(
requireContext
()).
isOpen
()){
isCameraNeedRelease
=
true
;
// 将相机生命周期和页面绑定,开启相机
CameraxController
.
getInstance
()
.
setLifecycleOwner
(
this
)
.
setOnImageAnalyzeListener
(
mViewModel
.
getOnImageAnalyzeListener
())
.
setFrameInterval
(
2
)
.
apply
();
}
else
{
// 注册相机图片监听
CameraxController
.
getInstance
()
.
setFrameInterval
(
2
)
.
setOnImageAnalyzeListener
(
mViewModel
.
getOnImageAnalyzeListener
());
}
// 设置预设坐标
mViewModel
.
setPresetCropValue
();
}
@Override
protected
void
initObserve
()
{
super
.
initObserve
();
// 观察裁剪坐标变化
mViewModel
.
croppedValue
.
observe
(
this
,
value
->
{
mViewModel
.
saveCropValue
(
value
);
});
}
@Override
protected
void
initListener
()
{
}
@Override
public
void
onDestroyView
()
{
super
.
onDestroyView
();
if
(
isCameraNeedRelease
)
{
CameraxController
.
getInstance
().
release
();
}
else
{
CameraxController
.
getInstance
().
setOnImageAnalyzeListener
(
null
);
}
}
@Override
...
...
module-setting/src/main/java/com/wmdigit/setting/fragment/SystemInfoFragment.java
View file @
dfa400a3
package
com
.
wmdigit
.
setting
.
fragment
;
import
android.graphics.Bitmap
;
import
com.elvishew.xlog.XLog
;
import
com.wmdigit.common.base.mvvm.BaseMvvmFragment
;
import
com.wmdigit.setting.R
;
import
com.wmdigit.setting.SettingActivity
;
import
com.wmdigit.setting.databinding.FragmentSystemInfoBinding
;
import
com.wmdigit.setting.viewmodel.SystemInfoViewModel
;
...
...
@@ -33,6 +29,7 @@ public class SystemInfoFragment extends BaseMvvmFragment<SystemInfoViewModel, Fr
protected
void
initData
()
{
// 绑定ViewModel和DataBinding
mDataBinding
.
setViewModel
(
mViewModel
);
}
@Override
...
...
module-setting/src/main/java/com/wmdigit/setting/viewmodel/CameraCropViewModel.java
View file @
dfa400a3
package
com
.
wmdigit
.
setting
.
viewmodel
;
import
android.app.Application
;
import
android.graphics.Bitmap
;
import
androidx.annotation.NonNull
;
import
androidx.lifecycle.MutableLiveData
;
import
com.wmdigit.camera.listener.OnImageAnalyzeListener
;
import
com.wmdigit.common.base.mvvm.BaseViewModel
;
import
com.wmdigit.common.base.mvvm.SingleLiveEvent
;
import
com.wmdigit.common.model.CropValue
;
import
com.wmdigit.common.utils.ParcelHelper
;
import
com.wmdigit.data.mmkv.repository.CropLocalRepository
;
/**
* 相机裁剪ViewModel
* @author dizi
*/
public
class
CameraCropViewModel
extends
BaseViewModel
{
/**
* 相机当前帧
*/
public
SingleLiveEvent
<
Bitmap
>
currentFrame
=
new
SingleLiveEvent
<>();
/**
* 裁剪坐标
*/
public
MutableLiveData
<
CropValue
>
croppedValue
=
new
MutableLiveData
<>();
/**
* 相机图片回调
*/
private
final
OnImageAnalyzeListener
onImageAnalyzeListener
=
bitmap
->
{
Bitmap
cloneBitmap
=
ParcelHelper
.
copy
(
bitmap
);
currentFrame
.
postValue
(
cloneBitmap
);
};
public
CameraCropViewModel
(
@NonNull
Application
application
)
{
super
(
application
);
}
public
OnImageAnalyzeListener
getOnImageAnalyzeListener
()
{
return
onImageAnalyzeListener
;
}
/**
* 设置预设裁剪坐标
*/
public
void
setPresetCropValue
(){
if
(
CropLocalRepository
.
getInstance
().
getHasCropped
())
{
croppedValue
.
postValue
(
CropLocalRepository
.
getInstance
().
getCropValue
());
}
}
/**
* 保存裁剪坐标
* @param value
*/
public
void
saveCropValue
(
CropValue
value
){
CropLocalRepository
.
getInstance
().
saveCropValue
(
value
);
}
}
module-setting/src/main/java/com/wmdigit/setting/viewmodel/SystemInfoViewModel.java
View file @
dfa400a3
...
...
@@ -9,7 +9,6 @@ import androidx.databinding.ObservableField;
import
com.elvishew.xlog.XLog
;
import
com.wmdigit.common.base.mvvm.BaseViewModel
;
import
com.wmdigit.common.utils.DeviceUtils
;
import
com.wmdigit.data.mmkv.constant.MmkvCons
;
import
com.wmdigit.data.mmkv.repository.CropLocalRepository
;
import
com.wmdigit.data.mmkv.repository.UserLocalRepository
;
...
...
@@ -87,7 +86,7 @@ public class SystemInfoViewModel extends BaseViewModel {
tenant
.
set
(
array
[
3
]);
}
// 获取裁剪状态
isCrop
.
set
(
CropLocalRepository
.
getInstance
().
get
IsCrop
());
isCrop
.
set
(
CropLocalRepository
.
getInstance
().
get
HasCropped
());
}
/**
...
...
module-setting/src/main/res/layout/fragment_camera_crop.xml
View file @
dfa400a3
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
>
xmlns:tools=
"http://schemas.android.com/tools"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
>
<data>
<variable
...
...
@@ -9,10 +10,89 @@
</data>
<FrameLayout
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
style=
"@style/fragment_root"
tools:context=
".fragment.CameraCropFragment"
>
<androidx.constraintlayout.widget.ConstraintLayout
style=
"@style/setting_module"
android:layout_height=
"match_parent"
android:layout_marginBottom=
"@dimen/dp_20"
>
<!--绿色图标-->
<View
android:id=
"@+id/icon_camera_crop"
style=
"@style/icon_title_green"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"@+id/tv_camera_crop"
app:layout_constraintBottom_toBottomOf=
"@+id/tv_camera_crop"
/>
<!--标题-->
<TextView
android:id=
"@+id/tv_camera_crop"
style=
"@style/text_base.title.module_title"
android:text=
"@string/module_setting_camera_area"
app:layout_constraintStart_toEndOf=
"@+id/icon_camera_crop"
app:layout_constraintTop_toTopOf=
"parent"
/>
<!--占位符1-->
<View
android:id=
"@+id/v_placeholder_1"
android:layout_width=
"@dimen/dp_0"
android:layout_height=
"@dimen/dp_0"
android:layout_marginStart=
"@dimen/dp_200"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
/>
<!--占位符2-->
<TextView
android:id=
"@+id/v_placeholder_2"
style=
"@style/text_base.content"
android:text=
"@string/module_setting_store_code2"
android:layout_marginEnd=
"@dimen/dp_10"
android:visibility=
"invisible"
app:layout_constraintTop_toTopOf=
"parent"
app:layout_constraintEnd_toStartOf=
"@+id/v_placeholder_1"
/>
<!--裁剪View-->
<com.wmdigit.common.view.imageview.BidirectionalDataBindingCropView
android:id=
"@+id/crop_view"
android:layout_width=
"640px"
android:layout_height=
"480px"
android:background=
"@color/black"
app:background_color=
"#66FFFFFF"
app:crop_enabled=
"true"
app:crop_mode=
"free"
app:frame_stroke_weight=
"@dimen/dp_2"
app:guide_color=
"#66FFFFFF"
app:guide_show_mode=
"show_always"
app:guide_stroke_weight=
"@dimen/dp_2"
app:handle_color=
"@android:color/white"
app:handle_show_mode=
"show_always"
app:handle_size=
"@dimen/dp_24"
app:handle_width=
"@dimen/dp_3"
app:initial_frame_scale=
"0.75"
app:min_frame_size=
"@dimen/dp_100"
app:overlay_color=
"#AA1C1C1C"
app:touch_padding=
"@dimen/dp_8"
android:layout_marginTop=
"@dimen/dp_40"
app:frame=
"@{viewModel.currentFrame}"
app:preset_crop_value=
"@={viewModel.croppedValue}"
app:layout_constraintStart_toStartOf=
"@+id/v_placeholder_2"
app:layout_constraintTop_toBottomOf=
"@+id/tv_camera_crop"
/>
<!--提示-->
<TextView
style=
"@style/text_base.content"
android:layout_marginTop=
"@dimen/dp_20"
android:text=
"@string/module_setting_tip_crop_camera"
app:layout_constraintStart_toStartOf=
"@+id/crop_view"
app:layout_constraintEnd_toEndOf=
"@+id/crop_view"
app:layout_constraintTop_toBottomOf=
"@+id/crop_view"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</layout>
\ No newline at end of file
module-setting/src/main/res/layout/fragment_system_info.xml
View file @
dfa400a3
...
...
@@ -247,7 +247,7 @@
<EditText
android:id=
"@+id/edt_camera_crop"
style=
"@style/edittext_base.w492"
android:textColor=
"@{viewModel.isCrop? @color/green : @color/red }"
android:textColor=
"@{viewModel.isCrop? @color/green
_008e75
: @color/red }"
android:text=
"@{viewModel.isCrop? @string/cropped : @string/not_crop}"
android:layout_marginEnd=
"@dimen/dp_100"
app:layout_constraintTop_toTopOf=
"@+id/edt_active_status"
...
...
module-setting/src/main/res/values/strings.xml
View file @
dfa400a3
...
...
@@ -26,6 +26,7 @@
<string
name=
"module_setting_please_input_tenant"
>
输入租户号
</string>
<string
name=
"module_setting_forget_register_info"
>
忘记注册信息
</string>
<string
name=
"module_setting_forget_register"
>
注册
</string>
<string
name=
"module_setting_tip_crop_camera"
>
调整识别区域,使其完全包裹置物台
</string>
<!-- TODO: Remove or change this placeholder text -->
<string
name=
"hello_blank_fragment"
>
Hello blank fragment
</string>
</resources>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment