感谢作者无缘公子的文章:https://blog.csdn.net/zuiwuyuan/article/details/52105176
本文根据自身需求做了修改,实现点击添加标记,此文做记录
后面追加了长按移动标记点功能
演示图
好了,不想废话,源码很简单,直接上:
public class ImageLayout extends FrameLayout implements View.OnClickListener {
private static final String TAG = "ImageLayout";
private ArrayList<PointBean> points;
private FrameLayout layouPoints;
private ImageView imgBg;
private OnClickViewPositionListener onClickViewPositionListener;
private OnClickMarkerListener onClickMarkerListener;
private int pointWidth; //图标宽
private int pointHeight;//图标高
private float startPositionW;//判断移动/点击
private float startPositionH;
private Context mContext;
private View imgPointLayout;
private int layoutHeight;
private int layoutWidth;
public ImageLayout(Context context) {
this( context, null );
}
public ImageLayout(Context context, AttributeSet attrs) {
this( context, attrs, 0 );
}
public ImageLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super( context, attrs, defStyleAttr );
initView( context, attrs );
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure( widthMeasureSpec, heightMeasureSpec );
}
/**
* wPercent:宽度百分比
* hPercent:高度百分比
*/
public interface OnClickViewPositionListener {
void onClickPercent(double wPercent, double hPercent);
}
/**
* 点击标记
*/
public interface OnClickMarkerListener {
void onClickPosition(int position);
}
public void setOnClickViewPositionListener(OnClickViewPositionListener onClickViewPositionListener) {
this.onClickViewPositionListener = onClickViewPositionListener;
}
public void setOnClickMarkerListener(OnClickMarkerListener onClickMarkerListener) {
this.onClickMarkerListener = onClickMarkerListener;
}
/**
* 初始化view
* @param context
* @param attrs
*/
private void initView(Context context, AttributeSet attrs) {
mContext = context;
pointWidth = dp2px( mContext, 20 );//图标宽
pointHeight = dp2px( mContext, 30 );//图标高
imgPointLayout = inflate( context, R.layout.layout_imgview_point, this );
imgBg = (ImageView) imgPointLayout.findViewById( R.id.imgBg );
layouPoints = (FrameLayout) imgPointLayout.findViewById( R.id.layouPoints );
}
/**
* 设置背景图
* @param width layout宽
* @param height layout高
* @param imgUrl 图片地址
*/
public void setImgBg(int width, int height, String imgUrl) {
layoutWidth = width;
layoutHeight = height;
ViewGroup.LayoutParams lp = imgBg.getLayoutParams();
lp.width = width;
lp.height = height;
imgBg.setLayoutParams( lp );
ViewGroup.LayoutParams lp1 = layouPoints.getLayoutParams();
lp1.width = width;
lp1.height = height;
layouPoints.setLayoutParams( lp1 );
Log.e( TAG, "setImgBg" + width + ":wh:" + height );
Glide.with( mContext ).load( imgUrl ).into( imgBg );
}
/**
* 设置点
* @param points 点
*/
public void setPoints(ArrayList<PointBean> points) {
if (layoutWidth <= 0 || layoutHeight <= 0) {
return;
}
if (points == null) {
return;
}
this.points = points;
layouPoints.removeAllViews();
for (int i = 0; i < points.size(); i++) {
double width_scale = points.get( i ).widthScale;
double height_scale = points.get( i ).heightScale;
LinearLayout view = (LinearLayout) LayoutInflater.from( mContext ).inflate( R.layout.layout_img_point, this, false );
ImageView imageView = (ImageView) view.findViewById( R.id.imgPoint );
imageView.setTag( i );
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
//根据需求自行调整icon位置
int w = (int) ((layoutWidth * width_scale) - pointWidth / 2);//x的中间
int h = (int) ((layoutHeight * height_scale) - pointHeight + pointHeight / 3);//图标y的底部
layoutParams.leftMargin = w;
layoutParams.topMargin = h;
imageView.setOnClickListener( this );
layouPoints.addView( view, layoutParams );
}
}
/**
* 触摸监听(获取点击手势)
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
/**
* 屏幕开始的位置
*/
case MotionEvent.ACTION_DOWN:
startPositionW = event.getX();
startPositionH = event.getY();
Log.e( TAG, "位置:(开始)x"+startPositionW + ",y" + startPositionH );
break;
/**
* 离开屏幕的位置
*/
case MotionEvent.ACTION_UP:
if (Math.abs( startPositionW - event.getX() ) < 100 && Math.abs( startPositionH - event.getY() ) < 100) {//点击
Log.e( TAG,"位置:(点击)" );
Log.e( TAG, "位置:(结束)百分比x"+getPercent( event.getX(), getMeasuredWidth() ) + ":y:" +
getPercent( event.getY(), getMeasuredHeight() ) );
if (onClickViewPositionListener != null) {
onClickViewPositionListener.onClickPercent( getPercent( event.getX(), getMeasuredWidth() )
, getPercent( event.getY(), getMeasuredHeight() ) );
}
} else {
//移动
Log.e( TAG, "位置:(移动)" );
}
break;
}
return true;
}
/**
* 将dp转换为与之相等的px
*/
public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
//百分比计算
public static double getPercent(double d, double e) {
double p = d / e;
DecimalFormat nf = (DecimalFormat) NumberFormat.getPercentInstance();
nf.applyPattern( "0.00" );
nf.setMaximumFractionDigits( 5 ); //表示精确到小数点后n位
return Double.parseDouble( nf.format( p ) );
}
@Override
public void onClick(View view) {
if (onClickMarkerListener != null){
int index = (int) view.getTag();
onClickMarkerListener.onClickPosition( index );
}
}
}
ImageLayout的xml:
layout_img_point.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/imgPoint"
android:layout_width="20dp"
android:layout_height="30dp"
android:src="@mipmap/position" />
</LinearLayout>
layout_imgview_point.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgBg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitXY" />
<FrameLayout
android:id="@+id/layouPoints"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</FrameLayout>
使用方式:
xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.imgmarker.ImageLayout
android:id="@+id/layoutContent"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="200dp"
android:layout_centerVertical="true"/>
</RelativeLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
private ImageLayout layoutContent;
private ArrayList<PointBean> pointSimples;
private String imgUrl;
Handler handler = new Handler( new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 12:
layoutContent.setImgBg( layoutContent.getMeasuredWidth(), layoutContent.getMeasuredHeight(), imgUrl );
layoutContent.setPoints( pointSimples );
break;
}
return false;
}
} );
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
layoutContent = findViewById( R.id.layoutContent );
initView();
initData();
//延迟加载,获取宽高
handler.sendEmptyMessageDelayed( 12, 1000 );
}
private void initView() {
layoutContent.setOnClickViewPositionListener( new ImageLayout.OnClickViewPositionListener() {
@Override
public void onClickPercent(final double wPercent, final double hPercent) {
Log.e( "位置:这是外部:", "wPercent:" + wPercent + "hPercent" + hPercent );
new AlertDialog.Builder( MainActivity.this )
.setMessage( "确定添加点?" )
.setPositiveButton( "确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
addPoint( wPercent, hPercent );
}
} )
.setNegativeButton( "取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
} ).show();
}
} );
layoutContent.setOnClickMarkerListener( new ImageLayout.OnClickMarkerListener() {
@Override
public void onClickPosition(int position) {
Toast.makeText( MainActivity.this, "index : " + position, Toast.LENGTH_SHORT ).show();
}
} );
}
private void addPoint(double wPercent, double hPercent) {
PointBean pointSimple = new PointBean( wPercent, hPercent );
pointSimples.add( pointSimple );
layoutContent.setPoints( pointSimples );
}
private void initData() {
// imgUrl = "https://img.zcool.cn/community/0136985bc6cd7ba8012099c8a6bd2b.jpg";
imgUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimgm.gmw.cn%2Fattachement%2Fjpg%2Fsite2%2F20161020%2Ff44d305ea09619727dab29.jpg&refer=http%3A%2F%2Fimgm.gmw.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1612596118&t=a65a12ac754e82362cc4a2031df29f33";
pointSimples = new ArrayList<>();
PointBean pointSimple100 = new PointBean();
pointSimple100.widthScale = 0.00f;
pointSimple100.heightScale = 0.00f;
PointBean pointSimple1 = new PointBean();
pointSimple1.widthScale = 0.20f;
pointSimple1.heightScale = 0.20f;
PointBean pointSimple2 = new PointBean();
pointSimple2.widthScale = 0.30f;
pointSimple2.heightScale = 0.30f;
PointBean pointSimple3 = new PointBean();
pointSimple3.widthScale = 0.40f;
pointSimple3.heightScale = 0.40f;
PointBean pointSimple4 = new PointBean();
pointSimple4.widthScale = 0.50f;
pointSimple4.heightScale = 0.50f;
PointBean pointSimple5 = new PointBean();
pointSimple5.widthScale = 0.50f;
pointSimple5.heightScale = 0.60f;
PointBean pointSimple6 = new PointBean();
pointSimple6.widthScale = 1f;
pointSimple6.heightScale = 1f;
pointSimples.add( pointSimple100 );
pointSimples.add( pointSimple1 );
pointSimples.add( pointSimple2 );
pointSimples.add( pointSimple3 );
pointSimples.add( pointSimple4 );
pointSimples.add( pointSimple5 );
pointSimples.add( pointSimple6 );
}
}
代码更新了:公司新要求,继上面功能,多加一个长按移动标记点功能
主要实现方式是通过标记点触摸监听,判断是长按的情况下,做位置移动更改
主要代码:
/**
* imageView监听
*
* @param view
* @param event
* @return
*/
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d( TAG, "onTouch: ACTION_DOWN" );
imgTimeDown = System.currentTimeMillis();
isLongClick = false;
imgDownX = event.getX();
imgDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.d( TAG, "onTouch: ACTION_MOVE" );
imgTimeMove = System.currentTimeMillis();
long durationMs = imgTimeMove - imgTimeDown;
Log.d( TAG, "onTouch: durationMs=" + durationMs );
if (Math.abs( imgDownX - event.getX() ) < 100 && Math.abs( imgDownY - event.getY() ) < 100 && durationMs > 500) {
isLongClick = true;
}
if (isLongClick) {
Log.d( TAG, "onTouch: isLongClick=" + isLongClick );
//长按事件,可以移动
//重新设置控件的位置。原位置加移动位置减去标记偏移量
float mX = view.getX() + event.getX() ;
float mY = view.getY() + event.getY() ;
//限制移动范围
if (mX < 0) {
mX = 0;
}
if (mX > getMeasuredWidth()) {
mX = getMeasuredWidth();
}
if (mY < 0) {
mY = 0;
}
if (mY > getMeasuredHeight()) {
mY = getMeasuredHeight();
}
view.setX( mX - pointWidthOffset);
view.setY( mY - pointHeightOffset);
}
break;
case MotionEvent.ACTION_UP:
Log.d( TAG, "onTouch: ACTION_UP" );
if (isLongClick) {//长按
Log.d( TAG, "onTouch: ACTION_UP是长按:" +
"tag:" + (int) view.getTag() +
"最终位置x:" + view.getX() + ":y:" + view.getY() );
if (onChangePositionListener != null) {
onChangePositionListener.onChangePercent( (int) view.getTag(), getPercent( view.getX()+pointWidthOffset, getMeasuredWidth() ),
getPercent( view.getY()+pointHeightOffset, getMeasuredHeight() ) );
}
}
if (Math.abs( imgDownX - event.getX() ) < 100 && Math.abs( imgDownY - event.getY() ) < 100 && !isLongClick) {//点击
if (onClickMarkerListener != null) {
int index = (int) view.getTag();
onClickMarkerListener.onClickPosition( index );
}
}
break;
}
return true;
}
更改之前的layout_img_point.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/imgPoint"
android:layout_width="20dp"
android:layout_height="30dp"
android:src="@mipmap/position">
</ImageView>
git图就不做了,现在贴出完整代码:
public class ImageLayout extends FrameLayout implements View.OnTouchListener {
private static final String TAG = "ImageLayout";
private ArrayList<PointBean> points;
private FrameLayout layouPoints;
private ImageView imgBg;
private OnClickViewPositionListener onClickViewPositionListener;
private OnClickMarkerListener onClickMarkerListener;
private OnChangePositionListener onChangePositionListener;
private int pointWidth; //图标宽
private int pointHeight;//图标高
private int pointWidthOffset;//偏移量
private int pointHeightOffset;
private float startPositionW;//判断移动/点击
private float startPositionH;
private Context mContext;
private View imgPointLayout;
private int layoutHeight;
private int layoutWidth;
private long imgTimeDown;
private boolean isLongClick;
private float imgDownX;
private float imgDownY;
private long imgTimeMove;
public ImageLayout(Context context) {
this( context, null );
}
public ImageLayout(Context context, AttributeSet attrs) {
this( context, attrs, 0 );
}
public ImageLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super( context, attrs, defStyleAttr );
initView( context, attrs );
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure( widthMeasureSpec, heightMeasureSpec );
}
/**
* 点击layout(空白区)
* wPercent:宽度百分比
* hPercent:高度百分比
*/
public interface OnClickViewPositionListener {
void onClickPercent(double wPercent, double hPercent);
}
/**
* 改变位置(改变标记点位置)
* wPercent:宽度百分比
* hPercent:高度百分比
*/
public interface OnChangePositionListener {
void onChangePercent(int position, double wPercent, double hPercent);
}
/**
* 点击标记
*/
public interface OnClickMarkerListener {
void onClickPosition(int position);
}
public void setOnClickViewPositionListener(OnClickViewPositionListener onClickViewPositionListener) {
this.onClickViewPositionListener = onClickViewPositionListener;
}
public void setOnClickMarkerListener(OnClickMarkerListener onClickMarkerListener) {
this.onClickMarkerListener = onClickMarkerListener;
}
public void setOnChangePositionListener(OnChangePositionListener onChangePositionListener) {
this.onChangePositionListener = onChangePositionListener;
}
/**
* 初始化view
*
* @param context
* @param attrs
*/
private void initView(Context context, AttributeSet attrs) {
mContext = context;
pointWidth = dp2px( mContext, 20 );//图标宽
pointHeight = dp2px( mContext, 30 );//图标高
pointWidthOffset = pointWidth / 2;//图标x偏移量
pointHeightOffset = (pointHeight / 3) * 2;//图标y偏移量
imgPointLayout = inflate( context, R.layout.layout_imgview_point, this );
imgBg = (ImageView) imgPointLayout.findViewById( R.id.imgBg );
layouPoints = (FrameLayout) imgPointLayout.findViewById( R.id.layouPoints );
}
/**
* 设置背景图
*
* @param width layout宽
* @param height layout高
* @param imgUrl 图片地址
*/
public void setImgBg(int width, int height, String imgUrl) {
layoutWidth = width;
layoutHeight = height;
ViewGroup.LayoutParams lp = imgBg.getLayoutParams();
lp.width = width;
lp.height = height;
imgBg.setLayoutParams( lp );
ViewGroup.LayoutParams lp1 = layouPoints.getLayoutParams();
lp1.width = width;
lp1.height = height;
layouPoints.setLayoutParams( lp1 );
Log.e( TAG, "setImgBg" + width + ":wh:" + height );
Glide.with( mContext ).load( imgUrl ).into( imgBg );
}
/**
* 设置点
*
* @param points 点
*/
public void setPoints(ArrayList<PointBean> points) {
if (layoutWidth <= 0 || layoutHeight <= 0) {
return;
}
if (points == null) {
return;
}
this.points = points;
layouPoints.removeAllViews();
for (int i = 0; i < points.size(); i++) {
double width_scale = points.get( i ).widthScale;
double height_scale = points.get( i ).heightScale;
ImageView imageView = (ImageView) LayoutInflater.from( mContext ).inflate( R.layout.layout_img_point, this, false );
imageView.setTag( i );
LayoutParams layoutParams = (LayoutParams) imageView.getLayoutParams();
//根据需求自行调整icon位置
int w = (int) ((layoutWidth * width_scale) - pointWidthOffset);//x的中间
int h = (int) ((layoutHeight * height_scale) - pointHeightOffset);//图标y的底部
layoutParams.leftMargin = w;
layoutParams.topMargin = h;
imageView.setOnTouchListener( this );
layouPoints.addView( imageView, layoutParams );
}
}
/**
* 触摸监听(获取点击手势)
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
/**
* 屏幕开始的位置
*/
case MotionEvent.ACTION_DOWN:
startPositionW = event.getX();
startPositionH = event.getY();
Log.e( TAG, "位置:(开始)x" + startPositionW + ",y" + startPositionH );
break;
/**
* 离开屏幕的位置
*/
case MotionEvent.ACTION_UP:
if (Math.abs( startPositionW - event.getX() ) < 100 && Math.abs( startPositionH - event.getY() ) < 100) {//点击
Log.e( TAG, "位置:(点击)" );
Log.e( TAG, "位置:(结束)百分比x" + getPercent( event.getX(), getMeasuredWidth() ) + ":y:" +
getPercent( event.getY(), getMeasuredHeight() ) );
if (onClickViewPositionListener != null) {
onClickViewPositionListener.onClickPercent( getPercent( event.getX(), getMeasuredWidth() )
, getPercent( event.getY(), getMeasuredHeight() ) );
}
} else {
//移动
Log.e( TAG, "位置:(移动)" );
}
break;
}
return true;
}
/**
* imageView监听
*
* @param view
* @param event
* @return
*/
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d( TAG, "onTouch: ACTION_DOWN" );
imgTimeDown = System.currentTimeMillis();
isLongClick = false;
imgDownX = event.getX();
imgDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.d( TAG, "onTouch: ACTION_MOVE" );
imgTimeMove = System.currentTimeMillis();
long durationMs = imgTimeMove - imgTimeDown;
Log.d( TAG, "onTouch: durationMs=" + durationMs );
if (Math.abs( imgDownX - event.getX() ) < 100 && Math.abs( imgDownY - event.getY() ) < 100 && durationMs > 500) {
isLongClick = true;
}
if (isLongClick) {
Log.d( TAG, "onTouch: isLongClick=" + isLongClick );
//长按事件,可以移动
//重新设置控件的位置。原位置加移动位置减去标记偏移量
float mX = view.getX() + event.getX() ;
float mY = view.getY() + event.getY() ;
//限制移动范围
if (mX < 0) {
mX = 0;
}
if (mX > getMeasuredWidth()) {
mX = getMeasuredWidth();
}
if (mY < 0) {
mY = 0;
}
if (mY > getMeasuredHeight()) {
mY = getMeasuredHeight();
}
view.setX( mX - pointWidthOffset);
view.setY( mY - pointHeightOffset);
}
break;
case MotionEvent.ACTION_UP:
Log.d( TAG, "onTouch: ACTION_UP" );
if (isLongClick) {//长按
Log.d( TAG, "onTouch: ACTION_UP是长按:" +
"tag:" + (int) view.getTag() +
"最终位置x:" + view.getX() + ":y:" + view.getY() );
if (onChangePositionListener != null) {
onChangePositionListener.onChangePercent( (int) view.getTag(), getPercent( view.getX()+pointWidthOffset, getMeasuredWidth() ),
getPercent( view.getY()+pointHeightOffset, getMeasuredHeight() ) );
}
}
if (Math.abs( imgDownX - event.getX() ) < 100 && Math.abs( imgDownY - event.getY() ) < 100 && !isLongClick) {//点击
if (onClickMarkerListener != null) {
int index = (int) view.getTag();
onClickMarkerListener.onClickPosition( index );
}
}
break;
}
return true;
}
/**
* 将dp转换为与之相等的px
*/
public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
//百分比计算
public static double getPercent(double d, double e) {
double p = d / e;
DecimalFormat nf = (DecimalFormat) NumberFormat.getPercentInstance();
nf.applyPattern( "0.00" );
nf.setMaximumFractionDigits( 5 ); //表示精确到小数点后n位
return Double.parseDouble( nf.format( p ) );
}
}
所有源码都在上面;
再附上demo一份,希望能帮助有需要的人(demo未更新,等我下次来更新吧)
地址:https://download.csdn.net/download/qq944639839/14040100