好久没写了,今天主要记录一下最近项目里用到的一个自定义View。
效果图不会搞,跳过。描述一下大概就是有很多图片,做一个展示,可以左右滑动来浏览图片,类似一个Gallery的效果,用RecyclerView做的。可以截取选中其中的一段,是用一个框,两边是两个手柄,拖动手柄可以调整选中的范围。
直接贴代码吧,测试代码里面有一些hardcode,不要介意。
Activity布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.example.testselectframgallery.MainActivity">
<com.example.testselectframgallery.MyGallery
android:layout_gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
可以看到就放了一个MyGallery。所以Activity代码就不放了。接下来是MyGallery的布局文件
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/my_gallery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00aa0000"
android:layout_gravity="center_vertical"/>
<com.example.testselectframgallery.MyGallerySelector
android:id="@+id/test_selector"
android:background="#00000000"
android:layout_width="match_parent"
android:layout_height="100px"/>
</merge>
MyGallery代码
package com.example.testselectframgallery;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
/**
* Created by kay.xu on 11/4/16.
*/
public class MyGallery extends FrameLayout{
private RecyclerView mRecyclerView;
private MyGallerySelector mGallerySelector;
public MyGallery(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.my_gallery_layout, this, true);
mRecyclerView = (RecyclerView) findViewById(R.id.my_gallery);
mGallerySelector = (MyGallerySelector) findViewById(R.id.test_selector);
Bitmap bitmap = drawableToBitmap(getResources().getDrawable(R.drawable.geo_opt_in_graphic, null));
MyGalleryAdapter adapter = new MyGalleryAdapter(getContext(), 100, 100);
for (int i = 0; i < 15; i ++) {
adapter.addBitmap(bitmap);
}
init(adapter, null, null, 5, 0, 14);
}
public void init(MyGalleryAdapter adapter, MyGalleryAdapter.OnItemClickListener onItemClickListener, OnItemSelectedListener onItemSelectedListener, int minItemCount, int start, int end) {
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(onItemClickListener);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mGallerySelector.onGalleryScrolled(dx);
}
});
mGallerySelector.init(adapter.getWidth(), minItemCount, start, end);
mGallerySelector.setOnItemSelectedListener(onItemSelectedListener);
}
public interface OnItemSelectedListener {
void onStartChanged(int index);
void onEndChanged(int index);
}
//这个是方便测试,直接把一张资源里面的图片转成bitmap,实际上很可能用不到
private Bitmap drawableToBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
}
MyGallery的Adapter
package com.example.testselectframgallery;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by kay.xu on 10/26/16.
*/
public class MyGalleryAdapter extends RecyclerView.Adapter<MyGalleryAdapter.ViewHolder> {
private List<Bitmap> mDatas;
private LayoutInflater mInflater;
private OnItemClickListener mOnItemClickListener;
private int mWidth, mHeight;
public MyGalleryAdapter(Context context, int width, int height) {
mInflater = LayoutInflater.from(context);
mDatas = new ArrayList<>();
mWidth = width;
mHeight = height;
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View arg0) {
super(arg0);
}
ImageView mImg;
}
@Override
public int getItemCount() {
return mDatas.size();
}
public void addBitmap(Bitmap image) {
mDatas.add(image);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = mInflater.inflate(R.layout.my_gallery_item_layout,
viewGroup, false);
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.mImg = (ImageView) view
.findViewById(R.id.my_gallery_item_image);
ViewGroup.LayoutParams params = viewHolder.mImg.getLayoutParams();
params.width = mWidth;
params.height = mHeight;
viewHolder.mImg.setLayoutParams(params);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int i) {
viewHolder.mImg.setImageBitmap(mDatas.get(i));
if (mOnItemClickListener != null) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(viewHolder.itemView, viewHolder.getPosition());
}
});
}
}
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
public void clear() {
if (mDatas != null) {
for (Bitmap bitmap : mDatas) {
bitmap.recycle();
bitmap = null;
}
}
mDatas.clear();
}
}
Adapter Item的布局文件
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 Tcl Corporation Limited -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/my_gallery_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
/>
</RelativeLayout>
选择图片的框(Selector)
package com.example.testselectframgallery;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
/**
* Created by kay.xu on 10/19/16.
*/
public class MyGallerySelector extends FrameLayout implements View.OnTouchListener{
private final Paint mPaintFill;
private final Paint mPaintStroke;
private MyHandle mHandleLeft;
private MyHandle mHandleRight;
private int mLastXLeft;
private int mLastYLeft;
private int mLastXRight;
private int mLastYRight;
private int mItemWidth;
private int mMinItemCount;
private static final int RADIUS = 20;
private static final int STROKE_WIDTH = 2;
private MyGallery.OnItemSelectedListener mOnItemSelectedListener;
private int mStart;
private int mEnd;
public MyGallerySelector(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.my_gallery_selector_layout, this, true);
mHandleLeft = (MyHandle) findViewById(R.id.handle_left);
mHandleRight = (MyHandle) findViewById(R.id.handle_right);
mHandleLeft.setOnTouchListener(this);
mHandleRight.setOnTouchListener(this);
mPaintFill = new Paint();
mPaintFill.setColor(0x700000ff);
mPaintStroke = new Paint();
mPaintStroke.setColor(Color.BLACK);
mPaintStroke.setStyle(Paint.Style.STROKE);
mPaintStroke.setStrokeWidth(STROKE_WIDTH);
}
public void init(int itemWidth, int minItemCount, int start, int end) {
mItemWidth = itemWidth;
mMinItemCount = minItemCount;
mStart = start;
mEnd = end;
scrollTo(start * itemWidth, 0);
}
public void onGalleryScrolled(int dx) {
scrollBy(dx, 0);
}
private void notifyOnItemSelected(View view) {
int location = (int) (view.getX() + view.getWidth()/2);
boolean isLeft = view == mHandleLeft;
int index = 0;
//if move over middle of the image,index +1
int offset = location % mItemWidth;
if (offset > mItemWidth/2) {
index += 1;
}
if (isLeft) {
index += location / mItemWidth;
if (mStart != index) {
mStart = index;
Log.d("xxx", "notifyOnItemSelected start :" + mStart);
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onStartChanged(mStart);
}
}
} else {
index += location / mItemWidth - 1;
if (mEnd != index) {
mEnd = index;
Log.d("xxx", "notifyOnItemSelected end :" + mEnd);
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onEndChanged(mEnd);
}
}
}
}
public void setOnItemSelectedListener(MyGallery.OnItemSelectedListener listener) {
this.mOnItemSelectedListener = listener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(
(mEnd +1) * mItemWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mHandleLeft.layout(mStart * mItemWidth - mHandleLeft.getMeasuredWidth()/2, 0, mStart * mItemWidth + mHandleLeft.getMeasuredWidth()/2, mHandleLeft.getMeasuredHeight());
mHandleRight.layout(getWidth() - mHandleLeft.getMeasuredWidth()/2, 0, getWidth() + mHandleLeft.getMeasuredWidth()/2, mHandleRight.getMeasuredHeight());
}
private void onMoveEnd(View movedView) {
float offset = (movedView.getX() + movedView.getWidth()/2) % mItemWidth;
boolean overdMiddle = offset > mItemWidth/2;
float difference = mItemWidth - offset;
if (overdMiddle) {
movedView.layout((int) (movedView.getLeft() + difference), movedView.getTop(), (int)(movedView.getRight() + difference), movedView.getBottom());
} else {
movedView.layout((int)(movedView.getLeft() - offset), movedView.getTop(), (int)(movedView.getRight() - offset), movedView.getBottom());
}
invalidate();
notifyOnItemSelected(movedView);
}
@Override
protected void onDraw(Canvas canvas) {
float left = mHandleLeft.getX() + mHandleLeft.getWidth()/2;
float right = mHandleRight.getX() + mHandleRight.getWidth()/2;
if (right > left) {
canvas.drawRoundRect(left, 0, right, getHeight(), RADIUS ,RADIUS, mPaintFill);
canvas.drawRoundRect(left, STROKE_WIDTH, right, getHeight() - STROKE_WIDTH, RADIUS ,RADIUS, mPaintStroke);
}else {
canvas.drawRoundRect(right, 0, left, getHeight(), RADIUS, RADIUS, mPaintFill);
canvas.drawRoundRect(right, STROKE_WIDTH, left, getHeight() - RADIUS, STROKE_WIDTH, RADIUS, mPaintStroke);
}
super.onDraw(canvas);
}
// private boolean isTouchInView(View view, MotionEvent event) {
// int touchX = (int) event.getRawX();
// int touchY = (int) event.getRawY();
// int[] viewLocation = new int[2];
// view.getLocationOnScreen(viewLocation);
// int viewLeft = viewLocation[0];
// int viewRight = viewLocation[0] + view.getWidth();
// int viewTop = viewLocation[1];
// int viewBottom = viewLocation[1] + view.getHeight();
// return touchX > viewLeft && touchX < viewRight && touchY > viewTop && touchY < viewBottom;
// }
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (v == mHandleLeft) {
mLastXLeft = (int) event.getRawX();
mLastYLeft = (int) event.getRawY();
} else {
mLastXRight = (int) event.getRawX();
mLastYRight = (int) event.getRawY();
}
break;
case MotionEvent.ACTION_MOVE:
int dx = (int) event.getRawX() - (v == mHandleLeft ? mLastXLeft : mLastXRight);
int dy = (int) event.getRawY() - (v == mHandleLeft ? mLastYLeft : mLastYRight);
//never move over min count.
if ((mHandleRight.getX() - mHandleLeft.getX() + (v == mHandleLeft ? -dx : dx)) < mMinItemCount * mItemWidth) {
return true;
}
v.layout(v.getLeft() + dx, v.getTop(), v.getRight() + dx, v.getBottom());
notifyOnItemSelected(v);
if (v == mHandleLeft) {
mLastXLeft = (int) event.getRawX();
mLastYLeft = (int) event.getRawY();
} else {
mLastXRight = (int) event.getRawX();
mLastYRight = (int) event.getRawY();
}
invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
onMoveEnd(v);
break;
}
return true;
}
}
Selector布局文件
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<com.example.testselectframgallery.MyHandle
android:id="@+id/handle_left"
android:layout_width="26dp"
android:layout_height="match_parent"
/>
<com.example.testselectframgallery.MyHandle
android:id="@+id/handle_right"
android:layout_width="26dp"
android:layout_height="match_parent"
/>
</merge>
最后就是手柄的代码
package com.example.testselectframgallery;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class MyHandle extends View {
private Paint mPaint;
public MyHandle(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.YELLOW);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth()/2, getHeight()/2,
getResources().getDimensionPixelSize(R.dimen.trimming_selector_handles_circle_radius),mPaint);
}
}
很简单,但我感觉还蛮实用的。另外这个由于用到了RecyclerView,需要添加一下jar包的依赖。