English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
No fim de semana, organizei alguns materiais de conhecimento sobre Android SwipeMenuListView(menu de deslize), a seguir está o conteúdo organizado:
SwipeMenuListView(menu de deslize)
Um menu de deslize para ListView.--Um excelente projeto de menu de deslize open-source.
Demo
1. Introdução
Estou vendo custom View e distribuição de eventos há um tempo, quero encontrar um projeto para praticar. Justamente para confirmar o que aprendi.
Encontrei esse projeto no github: SwipeMenuListView, é realmente bom, muito inspirador para a distribuição de eventos e View personalizada, apesar de algumas pequenas falhas, explicarei mais adiante. Quem quer entender como implementar o menu de deslize, esse artigo definitivamente ajudará, analisando detalhadamente cada arquivo de uma perspectiva macro e micro.
Endereço do projeto: https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 Versão: b00e0fe Sua utilização é simples, apenas três passos, você pode entender no github sem ocupar espaço, neste artigo analisamos apenas o princípio. Além disso, se você acha que o código é diferente do meu, e difícil de entender, pode ver o meu com comentários: http://download.csdn.net/detail/jycboy/9667699
Primeiro, vamos olhar para dois gráficos: para termos uma idéia geral.
São todas as classes do framework.
1.A seguir está a hierarquia de vistas:
Na imagem acima: SwipeMenuLayout é o layout do item ListView, dividido em duas partes, uma parte é o contentView exibido normalmente, e a outra parte é o menuView que pode ser arrastado; o SwipeMenuView herdado de LinearLayout, ao adicionar views, é adicionado horizontalmente, permitindo adicionar várias horizontalmente.
2.A seguir está a estrutura do diagrama de classes:
Abaixo está a relação de chamadas entre classes, ao lado das classes está indicada a função principal delas.
Dois, análise de código-fonte
SwipeMenu: SwipeMenuItem é uma classe de entidade, define atributos e métodos setter e getter, dê uma olhada. As anotações no código-fonte são basicamente claras.
2.1 SwipeMenuView: As instruções no código estão bem explicadas
/** * LinearLayout horizontal, que é o layout pai do entire swipe menu * Define métodos para adicionar itens e configurar atributos de itens * @author baoyz * @date 2014-8-23 * */ public class SwipeMenuView extends LinearLayout implements OnClickListener { private SwipeMenuListView mListView; private SwipeMenuLayout mLayout; private SwipeMenu mMenu; private OnSwipeItemClickListener onItemClickListener; private int position; public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } public SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView) { super(menu.getContext()); mListView = listView; mMenu = menu; // // conjunto de listas de MenuItem List<SwipeMenuItem> items = menu.getMenuItems(); int id = 0; //adiciona View construída a partir de item ao SwipeMenuView for (SwipeMenuItem item : items) { addItem(item, id++); } } /** * transforma MenuItem em componentes UI, um item é equivalente a um LinearLayout vertical, * SwipeMenuView é um LinearLayout horizontal, */ private void addItem(SwipeMenuItem item, int id) { //Parâmetros de layout LayoutParams params = new LayoutParams(item.getWidth(), LayoutParams.MATCH_PARENT); LinearLayout parent = new LinearLayout(getContext()); //Define id do menuitem, usado para distinguir eventos de clique posterior parent.setId(id); parent.setGravity(Gravity.CENTER); parent.setOrientation(LinearLayout.VERTICAL); parent.setLayoutParams(params); parent.setBackgroundDrawable(item.getBackground()); //Define listener parent.setOnClickListener(this); addView(parent); //Adiciona ao SwipeMenuView, horizontal if (item.getIcon() != null) { parent.addView(createIcon(item)); } if (!TextUtils.isEmpty(item.getTitle())) { parent.addView(createTitle(item)); } } //Cria img private ImageView createIcon(SwipeMenuItem item) { ImageView iv = new ImageView(getContext()); iv.setImageDrawable(item.getIcon()); return iv; } /*Cria title com base em parâmetros */ private TextView createTitle(SwipeMenuItem item) { TextView tv = new TextView(getContext()); tv.setText(item.getTitle()); tv.setGravity(Gravity.CENTER); tv.setTextSize(item.getTitleSize()); tv.setTextColor(item.getTitleColor()); return tv; } @Override /** * Usa o mLayout passado para determinar se está aberto * Chama o evento de clique onItemClick */ public void onClick(View v) { if (onItemClickListener != null && mLayout.isOpen()) { onItemClickListener.onItemClick(this, mMenu, v.getId()); } } public OnSwipeItemClickListener getOnSwipeItemClickListener() { return onItemClickListener; } /** * Define o evento de clique do item * @param onItemClickListener */ public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } public void setLayout(SwipeMenuLayout mLayout) { this.mLayout = mLayout; } /** * Interface de chamada de volta para eventos de clique */ public static interface OnSwipeItemClickListener { /** * Chama onItemClick no evento onClick * @param view Layout pai * @param menu Entidade menu * @param index ID do menuItem */ void onItemClick(SwipeMenuView view, SwipeMenu menu, int index); } }
**SwipeMenuViewé a View exibida ao deslizar, veja seu construtor SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView);遍历 Items: menu.getMenuItems(); chama o método addItem para adicionar8203;Adicionar item ao SwipeMenuView.
No método addItem: cada item é um LinearLayout。
2.2 SwipeMenuLayout :
Este código é um pouco longo, vamos dividir em três partes para analisar, apenas colar o código nuclear, o resto deve ser compreensível.
public class SwipeMenuLayout extends FrameLayout { private static final int CONTENT_VIEW_ID = 1; private static final int MENU_VIEW_ID = 2; private static final int STATE_CLOSE = 0; private static final int STATE_OPEN = 1; //Direção private int mSwipeDirection; private View mContentView; private SwipeMenuView mMenuView; ...... public SwipeMenuLayout(View contentView, SwipeMenuView menuView) { this(contentView, menuView, null, null); } public SwipeMenuLayout(View contentView, SwipeMenuView menuView, Interpolator closeInterpolator, Interpolator openInterpolator) { super(contentView.getContext()); mCloseInterpolator = closeInterpolator; mOpenInterpolator = openInterpolator; mContentView = contentView; mMenuView = menuView; //Definir SwipeMenuLayout para SwipeMenuView, para determinar se está aberto mMenuView.setLayout(this); init(); } private void init() { setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,) LayoutParams.WRAP_CONTENT)); mGestureListener = new SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { isFling = false; return true; } @Override //velocityX this parameter is the speed in the x-axis, negative to the left, positive to the right public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // TODO if (Math.abs(e1.getX() - e2.getX()) > MIN_FLING && velocityX < MAX_VELOCITYX) { isFling = true; } Log.i("tag","isFling="+isFling+" e1.getX()="+e1.getX()+" e2.getX()="+e2.getX()+ " velocityX="+velocityX+" MAX_VELOCITYX="+MAX_VELOCITYX); // Log.i("byz", MAX_VELOCITYX + ", velocityX = " + velocityX); return super.onFling(e1, e2, velocityX, velocityY); } }); mGestureDetector = new GestureDetectorCompat(getContext(), mGestureListener); 。。。。 LayoutParams contentParams = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); mContentView.setLayoutParams(contentParams); if (mContentView.getId() < 1) { //noinspection ResourceType mContentView.setId(CONTENT_VIEW_ID); } //noinspection ResourceType mMenuView.setId(MENU_VIEW_ID); mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); addView(mContentView); addView(mMenuView); }
Do método init acima, podemos ver que SwipeMenuLayout é composto por duas partes,分别是用户的item View e menu View .A operação de deslize do dedo é realizada por SimpleOnGestureListener .
/** * Evento de deslize, usado para interface de chamada externa * Este é um API exposto para o exterior, e o que chama este API é SwipeMenuListView, então MotionEvent é MotionEvent do SwipeMenuListView * @param event * @return */ public boolean onSwipe(MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX();//Registrar a coordenada x do clique isFling = false; break; case MotionEvent.ACTION_MOVE: // Log.i("byz", "downX = ") + mDownX + ", moveX = "; + event.getX()); int dis = (int) (mDownX - event.getX()); if (state == STATE_OPEN) {//Quando o estado é open, dis é 0 Log.i("tag", "dis = ") + dis);//Este valor sempre é 0 //DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1 dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1 Log.i("tag", "dis = ") + dis + ", mSwipeDirection = ") + mSwipeDirection); } Log.i("tag", "ACTION_MOVE downX = ") + mDownX + ", moveX = "; + event.getX()+", dis="+dis); swipe(dis); break; case MotionEvent.ACTION_UP: //Julgar a distância de deslize, abrir ou fechar //Aqui, se já houve um item aberto e, neste momento, deslizar outro item, ainda executa este método, como melhorar? if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) && Math.signum(mDownX - event.getX()) == mSwipeDirection) { Log.i("tag", "ACTION_UP downX = ") + mDownX + ", moveX = "; + event.getX()); // open smoothOpenMenu(); } else { // close smoothCloseMenu(); return false; } break; } return true; } public boolean isOpen() { return state == STATE_OPEN; } /** * Deslizar a distância dis, mover mContentView e mMenuView para a distância dis; * @param dis */ private void swipe(int dis) { if(!mSwipEnable){ return ; } //left é positivo; right é negativo; if (Math.signum(dis) != mSwipeDirection) {//;left = ;1;right = ;-1 dis = 0; //Não deslizar; } else if (Math.abs(dis) > mMenuView.getWidth()) {//Se for maior do que sua largura, dis é mMenuView.getWidth(); dis = mMenuView.getWidth();*mSwipeDirection; } //Ajustar o layout, mover continuamente para a esquerda (ou para a direita); mContentView.layout(;-dis, mContentView.getTop(), mContentView.getWidth(); -dis, getMeasuredHeight()); if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1 //Como acima, ajustar novamente o layout do menuview, a imagem é clara; mMenuView.layout(mContentView.getWidth(); - dis, mMenuView.getTop(), mContentView.getWidth(); + mMenuView.getWidth(); - dis,; mMenuView.getBottom()); } else { mMenuView.layout(-mMenuView.getWidth(); - dis, mMenuView.getTop(), - dis, mMenuView.getBottom()); } } /** * Atualizar o estado state = STATE_CLOSE; * Fechar menu */ public void smoothCloseMenu() { state = STATE_CLOSE; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mBaseX = ; -mContentView.getLeft(); //Deslizar a distância de mMenuView.getWidth() para ocultar completamente; mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350); } else { mBaseX = mMenuView.getRight(); mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350); } postInvalidate(); } public void smoothOpenMenu() { if(!mSwipEnable){ return ; } state = STATE_OPEN; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); Log.i("tag","mContentView.getLeft()="+mContentView.getLeft()+" mMenuView="+mMenuView.getWidth());//-451, que é a distância de movimento dis,-(downX-moveX) //mContentView.getLeft()=-540, mMenuView=540, os valores absolutos desses dois são iguais, completamente correto! Haha· } else { mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); } //Chame este método fora da thread UI para redesenhar a visão postInvalidate(); } .... }
Os principais métodos são onSwipe e swipe, cuja lógica principal é: onSwipe é exposto como API para chamadas externas,
No método de tratamento de eventos onTouchEvent da SwipeMenuListView, foi chamado o onSwipe; e swipe é mover mContentView e mMenuView uma distância dis8203;。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //A largura é infinitamente expandível, e a altura é especificada mMenuView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY)); } protected void onLayout(boolean changed, int l, int t, int r, int b) {}} mContentView.layout(0, 0, getMeasuredWidth(), mContentView.getMeasuredHeight()); if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//Deslizar para a esquerda //Relativamente ao view pai, com base na esquerda e no topo, escondido na direita mMenuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + mMenuView.getMeasuredWidth(), mContentView.getMeasuredHeight()); } else { //Deslizar para a direita, escondido na esquerda mMenuView.layout(-mMenuView.getMeasuredWidth(), 0, 0, mContentView.getMeasuredHeight()); } }
Os métodos onMeasure e onLayout acima são métodos frequentemente sobrescritos em View personalizados, onde onMeasure mede o tamanho do view, aqui definindo o tipo de largura como UNSPECIFIED, permitindo que se expanda infinitamente. onLayout ocorre após a medição do tamanho do view, onde o view é colocado na posição desejada no layout pai. O código mostra que o menuView é escondido na esquerda (ou direita) dependendo da direção do deslize.
2.3 SwipeMenuAdapter
public class SwipeMenuAdapter implements WrapperListAdapter, OnSwipeItemClickListener { private ListAdapter mAdapter; private Context mContext; private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener; public SwipeMenuAdapter(Context context, ListAdapter adapter) { mAdapter = adapter; mContext = context; } 。。。。 /** * Adicionar menu exibido durante a deslize * Aqui podemos ver que cada Item é um SwipeMenuLayout */ public View getView(int position, View convertView, ViewGroup parent) {}} SwipeMenuLayout layout = null; if (convertView == null) { View contentView = mAdapter.getView(position, convertView, parent);//view do item SwipeMenu menu = new SwipeMenu(mContext); //Criar SwipeMenu menu.setViewType(getItemViewType(position)); createMenu(menu); //Teste, pode-se deixar de lado SwipeMenuView menuView = new SwipeMenuView(menu, (SwipeMenuListView) parent); menuView.setOnSwipeItemClickListener(this); SwipeMenuListView listView = (SwipeMenuListView) parent; layout = new SwipeMenuLayout(contentView, menuView, listView.getCloseInterpolator(), listView.getOpenInterpolator()); layout.setPosition(position); } else { layout = (SwipeMenuLayout) convertView; layout.closeMenu(); layout.setPosition(position); View view = mAdapter.getView(position, layout.getContentView(), parent); } if (mAdapter instanceof BaseSwipListAdapter) { boolean swipEnable = (((BaseSwipListAdapter) mAdapter).getSwipEnableByPosition(position)); layout.setSwipEnable(swipEnable); } return layout; } //Este método é sobrescrito ao ser criado, aqui é apenas um teste, pode ser ignorado. public void createMenu(SwipeMenu menu) { // Teste de Código 。。。。。。 } /** * Método de chamada de volta de OnSwipeItemClickListener * Este método é sobrescrito quando a classe é criada. */ public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { if (onMenuItemClickListener != null) { onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index); } } 。。。。//Omitiu o que não é importante }
2.4 Classe principal: SwipeMenuListview,
Este código é longo, precisa de paciência para ler.
public class SwipeMenuListView extends ListView { private static final int TOUCH_STATE_NONE = 0; private static final int TOUCH_STATE_X = 1; private static final int TOUCH_STATE_Y = 2; public static final int DIRECTION_LEFT = 1; //Direção public static final int DIRECTION_RIGHT = -1; private int mDirection = 1;//deslizar da direita para a esquerda por padrão private int MAX_Y = 5; private int MAX_X = 3; private float mDownX; private float mDownY; private int mTouchState; private int mTouchPosition; private SwipeMenuLayout mTouchView; private OnSwipeListener mOnSwipeListener; //Cria menuItem private SwipeMenuCreator mMenuCreator; //Clique no evento de item do menuItem private OnMenuItemClickListener mOnMenuItemClickListener; private OnMenuStateChangeListener mOnMenuStateChangeListener; private Interpolator mCloseInterpolator; //动画变化率 private Interpolator mOpenInterpolator; //----adicionado por mim--下面这两行是我自己加的, //如果你下载此代码demo运行,你会发现,当一个item已经滑开时,滑动另一个item,此时原来打开的item没有关闭,可以看看QQ的侧滑,它是关闭的,我这里就稍微修改了下。 private int mOldTouchPosition = -1; private boolean shouldCloseMenu; //-------- public SwipeMenuListView(Context context) { super(context); init(); } public SwipeMenuListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public SwipeMenuListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } //初始化变量 private void init() { MAX_X = dp2px(MAX_X); MAX_Y = dp2px(MAX_Y); mTouchState = TOUCH_STATE_NONE; } @Override /** * 包装参数adapter成SwipeMenuAdapter */ public void setAdapter(ListAdapter adapter) { super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) { @Override public void createMenu(SwipeMenu menu) { if (mMenuCreator != null) { mMenuCreator.create(menu); } } @Override public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { boolean flag = false; if (mOnMenuItemClickListener != null) { flag = mOnMenuItemClickListener.onMenuItemClick( view.getPosition(), menu, index); } //Click the item in the list again to close the menu if (mTouchView != null && !flag) { mTouchView.smoothCloseMenu(); } } }); } ...... @Override //Intercept events, determine if the event is a click event or a sliding event public boolean onInterceptTouchEvent(MotionEvent ev) { //Handle at the intercept point, so that it can also swip at the click event set in the sliding, and the click event should not affect the original click event int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = ev.getX(); mDownY = ev.getY(); boolean handled = super.onInterceptTouchEvent(ev); mTouchState = TOUCH_STATE_NONE; //Change the state to none every time down occurs //Return the position of the item mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); //Get the view corresponding to the clicked item, which is SwipeMenuLayout View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //Only assign when empty to avoid assigning every time a touch occurs, which would result in multiple open states if (view instanceof SwipeMenuLayout) { //If there is an opened one, intercept. mTouchView is SwipeMenuLayout //If it's the same mTouchView twice, update mTouchView; if it's not the same view, intercept and return true if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) { Log.i("tag","Listview中的onInterceptTouchEvent ACTION_DOWN."); return true; } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection);//The default is left=1 } //If touched on another view, intercept this event if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) { handled = true; } if (mTouchView != null) { mTouchView.onSwipe(ev); } return handled; case MotionEvent.ACTION_MOVE: //Intercept events when moving, and handle them in onTouch float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) {}} //Cada interceptação de down define o estado de toque como TOUCH_STATE_NONE. Apenas ao retornar true é chamado onTouchEvent, então isso é suficiente aqui if (mTouchState == TOUCH_STATE_NONE) { if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } return true; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null) return super.onTouchEvent(ev); int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //O evento DOWN aqui pressupõe que o evento já foi interceptado, então as possíveis situações são:1.esse menu já está visível, clicou na área do item esquerdo //2.o menu já está visível, clicou em outro item //3.ao arrastar o item, primeiro DOWN e depois MOVE Log.i("tag","Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item"); int oldPos = mTouchPosition; //O design aqui não é razoável, após a chamada de onInterceptTouchEvent diretamente neste evento, mTouchPosition é o mesmo if(mOldTouchPosition == -1{//-1 é o valor original mOldTouchPosition = mTouchPosition; } mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//na lista //Aqui foi alterado, pldPos não é mais utilizado, agora é mOldTouchPosition if (mTouchPosition == mOldTouchPosition && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; //deslize na direção horizontal (横向) //Chame a interface de evento onSwipe() do SwipeMenuLayout mTouchView.onSwipe(ev); Log.i("tag","onTouchEvent ACTION_DOWN no Listview. Deslize ou clique em outro item"); return true; } if(mOldTouchPosition != mTouchPosition){ //quando a posição DOWN é diferente //shouldCloseMenu = true; mOldTouchPosition = mTouchPosition; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //Já há um menu aberto, neste momento, se clicar em outro item //Este método nunca será executado! if (mTouchView != null && mTouchView.isOpen()) { //Fechar swipeMenu mTouchView.smoothCloseMenu(); mTouchView = null; // return super.onTouchEvent(ev); // Tentar cancelar o evento de toque MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //Cancelar evento, tempo finalizado //Realizar chamada de volta para menu close if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; } if (view instanceof SwipeMenuLayout) { mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); } if (mTouchView != null) { mTouchView.onSwipe(ev); } break; case MotionEvent.ACTION_MOVE: //Alguns podem ter header, é necessário subtrair header antes de determinar mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount(); //Se houver um deslize parcialmente revelado, recolha-o, neste momento mTouchView já foi atribuído, e não é permitido deslizar outro view que não pode swip //Isso causaria mTouchView swip. Portanto, é necessário usar a posição para determinar se a deslize é um view if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) { break; } float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (mTouchState == TOUCH_STATE_X) { //Em direção ao eixo X if (mTouchView != null) { mTouchView.onSwipe(ev); //chamar evento de deslize } getSelector().setState(new int[]{0}); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev);//Fim do evento return true; } else if (mTouchState == TOUCH_STATE_NONE) {//O evento DOWN seguido por Move if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP: //fechou o menu Log.i("tag","ACTION_UP do onTouchEvent"); if (mTouchState == TOUCH_STATE_X) { if (mTouchView != null) { Log.i("tag","Por que o ACTION_UP do onTouchEvent não fechou"); boolean isBeforeOpen = mTouchView.isOpen(); //chamar evento de deslize mTouchView.onSwipe(ev); boolean isAfterOpen = mTouchView.isOpen(); if (isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener != null) { if (isAfterOpen) { mOnMenuStateChangeListener.onMenuOpen(mTouchPosition); } else { mOnMenuStateChangeListener.onMenuClose(mTouchPosition); } } if (!isAfterOpen) { mTouchPosition = -1; mTouchView = null; } } if (mOnSwipeListener != null) { //realizar chamada de volta para o fim da deslize mOnSwipeListener.onSwipeEnd(mTouchPosition); } ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } break; } return super.onTouchEvent(ev); } public void smoothOpenMenu(int position) { if (position >= getFirstVisiblePosition() && position <= getLastVisiblePosition()) { View view = getChildAt(position - getFirstVisiblePosition()); if (view instanceof SwipeMenuLayout) { mTouchPosition = position; if (mTouchView != null && mTouchView.isOpen()) { mTouchView.smoothCloseMenu(); } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); mTouchView.smoothOpenMenu(); } } } /** * Pode entrar no código-fonte original, que converte diferentes unidades em pixels px, aqui é dp->px * @param dp * @return */ private int dp2px(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); } public static interface OnMenuItemClickListener { boolean onMenuItemClick(int position, SwipeMenu menu, int index); } public static interface OnSwipeListener { void onSwipeStart(int position); void onSwipeEnd(int position); } public static interface OnMenuStateChangeListener { void onMenuOpen(int position); void onMenuClose(int position); } 。。。。 }
A lógica mais importante dessa classe é sobre a decisão e distribuição de eventos, quando interceptar eventos, e que operação corresponde a diferentes eventos. Se você não entender bem a distribuição de eventos, pode procurar blogs relacionados na internet ou ler meus blogs posteriores, que devem ser publicados em alguns dias.
Aqui está a análise da lógica de distribuição de eventos do SwipeMenuListView: o núcleo é o tratamento do evento de clique e deslize do item no SwipeMenuListView. Quando há deslize, o SwipeMenuListView intercepta o evento, trata-o sozinho e lembra dessa lógica, é fácil entender com o código. Abaixo está um diagrama de fluxo de distribuição de eventos que eu desenhei:
O evento de toque é uma sequência de eventos: ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. Start with ACTION_DOWN and end with ACTION_UP.
Below is my printed process: (Add log in the code yourself)
I/tag: ACTION_DOWN of onInterceptTouchEvent in Listview. view=class com.baoyz.swipemenulistview.SwipeMenuLayout I/tag: ACTION_DOWN handled=false of onInterceptTouchEvent I/tag: SwipeMenuLayout onTouchEvent I/tag: ACTION_DOWN of onTouchEvent in Listview. Did you click another item I/tag: oldPos=1 mTouchPosition=1 I/tag: ACTION_MOVE downX = 987, moveX = 906.69666, dis=80 I/tag: ACTION_MOVE downX = 987, moveX = 855.5785, dis=131 I/tag: ACTION_MOVE downX = 987, moveX = 797.6258, dis=189 I/tag: ACTION_MOVE downX = 987, moveX = 735.9639, dis=251 I/tag: ACTION_MOVE downX = 987, moveX = 666.5104, dis=320 I/tag: ACTION_MOVE downX = 987, moveX = 589.0626, dis=397 I/tag: ACTION_MOVE downX = 987, moveX = 509.15567, dis=477 I/tag: ACTION_MOVE downX = 987, moveX = 431.7224, dis=555 I/tag: ACTION_MOVE downX = 987, moveX = 361.2613, dis=625 I/tag: ACTION_MOVE downX = 987, moveX = 319.70398, dis=667 I/tag: ACTION_UP of onTouchEvent event I/tag: Why ACTION_UP of onTouchEvent event is not closed I/tag: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500 I/tag: ACTION_UP downX = 987, moveX = 319.70398 I/tag: mContentView.getLeft()=-540, mMenuView=540
Three, existing problems
1.If you run the framework down, you will find a problem:
When a ListView item has been slid open, assume it is item1;At this time, slide another item, call it item2;
In this case, item1It will not close, item2Of course, it will not open.
This effect is not good, and I have modified this problem in the code. The specific code is marked.
2.It is the following code: In the ACTION_DOWN of onTouchEvent(MotionEvent ev) in SwipeMenuListView, this code will never be executed becauseonTouchEvent and onInterceptTouchEventCorresponding MotionEvent.
mTouchPosition == oldPosEternal equality.
//Este método nunca será executado! A intenção do autor é fechar o Menu quando mTouchPosition != oldPos, mas de acordo com este código, esses valores sempre serão iguais. //Claro que são iguais, pois correspondem a um MotionEvent if (mTouchView != null && mTouchView.isOpen()) { //Fechar swipeMenu mTouchView.smoothCloseMenu(); //mTouchView = null; // return super.onTouchEvent(ev); // Tentar cancelar o evento de toque MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //Cancelar evento, tempo finalizado //Realizar chamada de volta para menu close if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; }
Eu já modifiquei esse problema no código. Atualmente, já submeti ao autor original no github.
Obrigado por ler, esperamos que ajude a todos, obrigado pelo apoio ao site!
Declaração: O conteúdo deste artigo é extraído da internet, pertence ao respectivo proprietário, foi contribuído e carregado voluntariamente pelos usuários da internet, o site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidade legal relevante. Se você encontrar conteúdo suspeito de violação de direitos autorais, por favor, envie um e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar e forneça provas relevantes. Apenas após a verificação, o site deletará o conteúdo suspeito de violação de direitos autorais.)