因公司開發需要,以前在實現列表展示後的修改刪除等操作都是在recyclerview的長按事件中進行實現的,慢慢的接觸到了自定義View 就想着自定義一個仿QQ的實現側滑的效果,期間也查看了很多大神的文檔,畢竟剛開始學習自定義view,所以還是有些生疏。。。。廢話不多嗶嗶,開始開始
效果圖:
首先要實現列表 我們需要適配器,子佈局還有數據 我們就來一點一點實現
item佈局:
<?xml version="1.0" encoding="utf-8"?>
<com.example.zidingyidemo.Second.SlideLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/ll_content_view"
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:visibility="visible">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_test2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_toEndOf="@id/iv_avatar"
android:text="好友名稱"
android:textColor="#000000"
android:textSize="18sp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_avatar" />
<TextView
android:id="@+id/tv_test3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_test2"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_toEndOf="@id/iv_avatar"
android:maxLines="1"
android:text="內容展示,隨便寫一些東西測試一下就好"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_avatar" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:text="昨天"
android:layout_alignParentRight="true"
android:layout_marginLeft="10dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="200dp"
android:layout_height="70dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_toFirst"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="置頂"
android:textColor="@android:color/white"
android:textSize="22sp" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_red_light"
android:gravity="center"
android:text="刪除"
android:textColor="@android:color/white"
android:textSize="22sp" />
</LinearLayout>
</com.example.zidingyidemo.Second.SlideLayout>
首先我們定義了一個自定義的組件,然後將我們要顯示的內容進行填充,其中還包括我們需要的置頂,刪除的功能 其中用的textview進行顯示
然後來看一下我們的自定義View:
public class SlideLayout extends RelativeLayout {
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
}
好了 這樣我們的項目就簡單的搭建好了 然後我們來一點點的實現效果
private Scroller mScroller;
private View mContentView;
private View mMenuView;
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContentView = getChildAt(0);//自定義組件的一級子view->這裏爲RelativeLayout
mMenuView = getChildAt(1);//自定義組件的二級子view->這裏爲LinearLayout
}
首先在我們的自定義view初始化的時候 我們將scroller先定義出來 ,然後我們可以重寫onFinishInflate()這個方法,這個方法的意思是在自定義View初始化或者xml初始化結束之後調用 然後我們可以在裏面拿到一級二級子view
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//將menu佈局到右側不可見(屏幕外)
//720,0,720+400,140
mMenuView.layout(mContentWidth, 0, mContentWidth + mMenuWidth, mMenuHeight);
Log.i("mMenuWidth","@"+mMenuWidth);
Log.i("mContentWidth","@"+mContentWidth);
Log.i("mMenuHeight","@"+mMenuHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
onLayout()是確定View和孩子的位置 然後我們可以在這裏指定我們的置頂刪除的位置 詳細的參數意思我就不說了
private float startX;
private float downX;
private float downY;
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x; //獲取x的起始座標
break;
case MotionEvent.ACTION_MOVE:
final float dx = (int) (x - startX);//拿到偏移量
Log.i("ssinstance","@"+dx);
Log.i("ssgetScrollX","@"+getScrollX());
int disX = (int) (getScrollX() - dx); //其中的getScrollx()座標的獲取是根據起始座標減去移動後View試圖左上角的值
Log.i("movemove","@"+disX);
if (disX <= 0) {
disX = 0;
}
scrollTo(Math.min(disX, mMenuWidth), getScrollY());
final float moveX = Math.abs(x - downX);
final float moveY = Math.abs(y - downY);
if (moveX > moveY && moveX > 10f) {
//父佈局不要攔截子view的touch事件
getParent().requestDisallowInterceptTouchEvent(true);
}
startX = x;
break;
return true;
}
然後我們重寫onTouchEvent()方法 監聽當前的x,y座標的改變
根據上圖 我們設置屏幕的寬度爲720 高度爲140 置頂刪除的寬度爲400 高度爲140
思路:
假設我們當前的按下的x爲700 向左進行移動 move的最終值爲500 這樣他的偏移量就爲500-700 = -200
我們的getScrollX()值就爲置頂刪除的寬度 就爲400 然後用400–200 = 600
然後我們進行判斷 最終移動的位置就爲-400,0
final float moveX = Math.abs(x - downX);
final float moveY = Math.abs(y - downY);
if (moveX > moveY && moveX > 10f) {
//父佈局不要攔截子view的touch事件
getParent().requestDisallowInterceptTouchEvent(true);
}
startX = x;
break;
這裏的主要意思是先進行判斷移動的距離 太小的話監聽沒必要
case MotionEvent.ACTION_UP:
Log.i("upup","@"+getScrollX());
if (getScrollX() < mMenuWidth / 2) {//判斷向左移動的距離
closeMenu();//不顯示置頂刪除
} else {
openMenu();//顯示置頂刪除
}
break;
//如果當前方法返回true,攔截事件 並會觸發當前控件的onTouchEvent方法 else 繼續往下傳遞
//攔截事件不傳遞給子view
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = x;
downY = y;
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onClick(this);
}
break;
case MotionEvent.ACTION_MOVE:
final float moveX = Math.abs(x - downX);
if (moveX > 10f) { //對touch事件進行攔截
intercept = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
@Override
public void computeScroll() { //當我們調用invalidate(); 時會執行
super.computeScroll();
//當動畫執行完成以後,執行新的動畫
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
public final void openMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), mMenuWidth - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuOpen(this);
}
}
public final void closeMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), 0 - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuClose(this);
}
}
//自定義事件回調
public interface onSlideChangeListener {
void onMenuOpen(SlideLayout slideLayout);
void onMenuClose(SlideLayout slideLayout);
void onClick(SlideLayout slideLayout);
}
public void setOnSlideChangeListener(onSlideChangeListener onSlideChangeListener1) {
this.mOnSlideChangeListener = onSlideChangeListener1;
}
這些很抽象 我也不知道怎麼詳細介紹 您就自己看看吧
.
完整的自定義View的代碼:
public class SlideLayout extends RelativeLayout {
private View mContentView;
private View mMenuView;
private int mMenuWidth;
private int mMenuHeight;
private int mContentWidth;
private Scroller mScroller;
private float startX;
private float downX;
private float downY;
private onSlideChangeListener mOnSlideChangeListener;
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContentView = getChildAt(0);//內容的父容器
mMenuView = getChildAt(1);//置頂刪除的父容器
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mContentWidth = getMeasuredWidth();//屏幕寬度
// mContentHeight = getMeasuredHeight();
mMenuWidth = mMenuView.getMeasuredWidth();//置頂刪除父容器的寬
mMenuHeight = mMenuView.getMeasuredHeight();//置頂刪除父容器的高
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//將menu佈局到右側不可見(屏幕外)
//720,0,720+400,140
mMenuView.layout(mContentWidth, 0, mContentWidth + mMenuWidth, mMenuHeight);
Log.i("mMenuWidth","@"+mMenuWidth);
Log.i("mContentWidth","@"+mContentWidth);
Log.i("mMenuHeight","@"+mMenuHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
break;
case MotionEvent.ACTION_MOVE:
final float dx = (int) (x - startX);
Log.i("ssinstance","@"+dx);
Log.i("ssgetScrollX","@"+getScrollX());
int disX = (int) (getScrollX() - dx);
Log.i("movemove","@"+disX);
if (disX <= 0) {
disX = 0;
}
scrollTo(Math.min(disX, mMenuWidth), getScrollY());
final float moveX = Math.abs(x - downX);
final float moveY = Math.abs(y - downY);
if (moveX > moveY && moveX > 10f) {
//父佈局不要攔截子view的touch事件
getParent().requestDisallowInterceptTouchEvent(true);
}
startX = x;
// startY = y;
break;
case MotionEvent.ACTION_UP:
Log.i("upup","@"+getScrollX());
if (getScrollX() < mMenuWidth / 2) {
closeMenu();
} else {
openMenu();
}
break;
}
return true;
}
//如果當前方法返回true,攔截事件 並會觸發當前控件的onTouchEvent方法 else 繼續往下傳遞
//攔截事件不傳遞給子view
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = x;
downY = y;
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onClick(this);
}
break;
case MotionEvent.ACTION_MOVE:
final float moveX = Math.abs(x - downX);
if (moveX > 10f) { //對touch事件進行攔截
intercept = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
@Override
public void computeScroll() {
super.computeScroll();
//當動畫執行完成以後,執行新的動畫
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
public final void openMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), mMenuWidth - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuOpen(this);
}
}
public final void closeMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), 0 - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuClose(this);
}
}
public interface onSlideChangeListener {
void onMenuOpen(SlideLayout slideLayout);
void onMenuClose(SlideLayout slideLayout);
void onClick(SlideLayout slideLayout);
}
public void setOnSlideChangeListener(onSlideChangeListener onSlideChangeListener1) {
this.mOnSlideChangeListener = onSlideChangeListener1;
}
}
然後來看適配器
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<String> arrayList;
private Context mContext;
private SlideLayout mSlideLayout;
public MyAdapter(Context context, ArrayList<String> dataList) {
this.arrayList = dataList;
this.mContext = context;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, null));
}
@Override
public void onBindViewHolder(final MyViewHolder myViewHolder, int position) {
myViewHolder.textView.setText(arrayList.get(position));
myViewHolder.contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext, "item被點擊", Toast.LENGTH_SHORT).show();
}
});
// myViewHolder.contentView.setOnLongClickListener(new View.OnLongClickListener() {
// @Override
// public boolean onLongClick(View view) {
// int[] location = new int[2];
// view.getLocationOnScreen(location);
// View view1 = LayoutInflater.from(mContext).inflate(R.layout.item_background_popwindow, null);
// PopupWindow popupWindow = new PopupWindow(view1, 300, 150);
// popupWindow.setContentView(view1);
// popupWindow.setOutsideTouchable(false);
// popupWindow.setFocusable(true);
// popupWindow.showAtLocation(view, Gravity.NO_GRAVITY, (view.getWidth() - popupWindow.getWidth()) / 2, location[1] - popupWindow.getHeight() - 5);
// return false;
// }
// });
myViewHolder.to_first.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//position在remove後會變,所以先把內容取出來
String content = arrayList.get(myViewHolder.getAdapterPosition());
arrayList.remove(myViewHolder.getAdapterPosition());
arrayList.add(0, content);
notifyDataSetChanged();
}
});
myViewHolder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
arrayList.remove(myViewHolder.getAdapterPosition());
notifyDataSetChanged();
}
});
mSlideLayout = (SlideLayout) myViewHolder.itemView;
mSlideLayout.setOnSlideChangeListener(new SlideLayout.onSlideChangeListener() {
@Override
public void onMenuOpen(SlideLayout slideLayout) {
mSlideLayout = slideLayout;
}
@Override
public void onMenuClose(SlideLayout slideLayout) {
if (mSlideLayout != null) {
mSlideLayout = null;
}
}
@Override
public void onClick(SlideLayout slideLayout) {
if (mSlideLayout != null) {
mSlideLayout.closeMenu();
}
}
});
}
@Override
public int getItemCount() {
return arrayList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private TextView to_first;
private TextView delete;
private RelativeLayout contentView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_test2);
to_first = itemView.findViewById(R.id.tv_toFirst);
delete = itemView.findViewById(R.id.tv_delete);
contentView = itemView.findViewById(R.id.ll_content_view);
}
}
}
這個就不做過多的解釋了
actiity:
private void initView() {
recyclerView = findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
for(int i=0;i<20;i++){
data.add("我是第"+i+"個內容");
}
MyAdapter adapter = new MyAdapter(this,data);
recyclerView.setAdapter(adapter);
}
ok 這就完了 其中的自定義事件回調 一級事件攔截