English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
No projeto, é necessário a função de atualização de arrasto, mas este View não é um controle do tipo ListView, precisa implementar essa função através de ViewGroup, inicialmente procurei um pouco na internet, não encontrei nada especialmente adequado, também não entendi muito o código, então decidi escrever um por conta própria.
Então, tirei o código-fonte do XlistView para ver um pouco a cada vez, e finalmente compreendi o código-fonte do XLisview, decidi finalmente fazer isso eu mesmo
Para facilitar, a headView ainda usou HeadView do XListView, economizando muito trabalho:)
Atualização de arrasto, atualização de arrasto, é claro que é necessário implementar a função de arrasto primeiro, inicialmente eu estava pensando em usar extends ScrollView para implementar, porque há efeitos de rolagem prontos, mas finalmente desisti por dois motivos:
1, Only one child View can be under ScrollView, although you can add a ViewGroup under Scroll and then dynamically add headView to the front ViewGroup, but I still prefer studio's visual preview, I always feel it's not intuitive!
2, When a ListView is nested within a ScrollView, a conflict will occur, and it is also necessary to rewrite ListView. So, I give up and change the idea!
Regarding the above reason1: Dynamically add headView to the ScrollView's middle GroupView, you can override the ScrollView's onViewAdded() method to add the initialized headView to the child GroupView
@Override public void onViewAdded(View child) { super.onViewAdded(child); //Since headView needs to be on top, the first thought is Vertical LinearLayout LinearLayout linearLayout = (LinearLayout) getChildAt(0); linearLayout.addView(view, 0); }
Change the thinking, implement it by extending LinearLayout!
First do the preparation work, we need a HeaderView and the height of HeaderView to be obtained, as well as the initial height of Layout
private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setOrientation(VERTICAL); addView(mHeaderView, 0); getHeaderViewHeight(); getViewHeight(); }
mHeaderView = new SRefreshHeader(context);
HeaderView is instantiated through the constructor
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
This is to parse the headerView content areaiew, I'll get the height of this view in a moment, you must be wondering why not use the above mHeaderView to get the height, you can see the following code by entering the constructor method
// Em situação inicial, definir a altura da view de atualização下拉 para 0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); w(mContainer, lp);
If you directly get the height of mHeaderView, it must be 0
getHeaderViewHeight();
getViewHeight();
It is to obtain the height of HeaderView and the initial height of Layout
/** * Obter a altura do headView */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * Obter a altura da instância atual de SRefreshLayout */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); }
Preparation is complete, next is to perform the pull-down operation
By now, you must have thought of the on_TOUCH_EVENT() method, that's right! Let's start building here now
To implement pull down, it will go through three processes
ACTION_UP→ACTION_MOVE→ACTION_UP
No evento ACTION_UP, ou seja, quando o dedo é pressionado, tudo o que precisamos fazer é registrar a coordenada do toque quando foi pressionado
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //记录起始高度 mLastY = ev.getRawY();//记录按下时的Y坐标 break;
Depois disso, é o evento ACTION_MOVE, que é o mais importante, pois as mudanças de altura do HeadView e do Layout ao puxar para baixo são realizadas aqui
case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY(); - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//Reduzir a distância de movimento em uma certa proporção updateHeight(); break;
Dentro deles, updateHeaderViewHeight e updateHeight são métodos para alterar a altura do HeaderView e a altura do Layout
private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //Atualizar a altura da instância atual do layout para a altura do headerView mais a altura inicial do layout //Se não atualizar o layout, comprimirá a altura do conteúdo, impossível manter a proporção lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //Se não estiver em refresh e se a altura if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); }
Ao atualizar a altura do Header, determina-se se foi atingido o distância de atualização puxando para baixo, no código acima, estou definindo que, ao atingir o dobro da altura inicial do mHeaderView, entra no estado de 'liberar atualização', se não for atingido, mantém o estado de 'atualizar arrastando para baixo'
O estado do HeaderView define um total de3Os respectivos são
public final static int STATE_NORMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//释放刷新 public final static int STATE_REFRESHING = 2;//正在刷新
O método de atualização da altura é o mesmo para headerView e layout, é adicionar a distância móvel à altura original e atribuir novamente ao headerView ou ao layout
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
Por fim, é o evento ACTION_UP, que é quando o dedo sai da tela, aqui precisamos decidir o estado final do headerView com base no estado atual do headerView!
case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break;
resetHeadView e reset são métodos para resetar a altura do headerView e do layout
private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_NORMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_NORMAL: mHeaderView.setVisiableHeight(0); break; } }
A maneira de implementação é a mesma. Baseado no estado, se estiver em estado de atualização, o headerView deve ser exibido normalmente e a altura deve ser a inicial, se estiver em NORMAL, ou seja, no estado de 'atualizar arrastando para baixo', diz que não foi acionado a atualização, ao resetar, o headerView deve ser ocultado, ou seja, a altura deve ser resetada para 0
Até aqui, a operação de atualização de arrastar para baixo também está praticamente completa, ainda é necessário adicionar uma interface de callback para notificação
interface OnRefreshListener { void onRefresh(); }
case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break;
Bom, até aqui, praticamente está completo, teste o efeito. Ei, notei um problema, por que este Layout não pode executar a atualização de arrastar para baixo quando usado dentro de um ListView aninhado! Pense bem, deve ser um problema de distribuição de eventos, precisa ser tratado!
Sobre o tratamento de interceptação de eventos, li o blog de distribuição de eventos do ViewGroup do grande mestre Hongyang e do Android-Ultra-Pull-To-A parte do código do Refresh, onde encontrei a solução:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("Encontrado listView"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY() - mStartY; Logs.v("space:" + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("拦截成功"); return true; } else { Logs.v("不拦截"); return false; } } return super.onInterceptTouchEvent(ev); }
Dentre eles
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
Space é a distância móvel canScrollVertically() é usado para determinar se o ListView pode rolar na direção vertical, quando o parâmetro é negativo, representa rolagem para cima, quando é positivo, representa rolagem para baixo, e o último é a posição do primeiro item visível do ListView
Com o tratamento de interceptação de eventos acima, um ViewGroup que atende às necessidades mencionadas no início também está completo!
Aqui está o código-fonte do Layout e do HeaderView (usado diretamente pelo XlistView's HeaderView)
public class SRefreshLayout extends LinearLayout { private SRefreshHeader mHeaderView; private RelativeLayout mHeaderViewContent; private boolean isRefreashing; private float mLastY = -1;//Altura de início da pressão private int mHeaderViewHeight;//Altura do conteúdo do headerView private int mHeight;//Altura do layout private float mStartY; interface OnRefreshListener { void onRefresh(); } public OnRefreshListener mOnRefreshListener; public SRefreshLayout(Context context) { super(context); initView(context); } public SRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setOrientation(VERTICAL); addView(mHeaderView, 0); getHeaderViewHeight(); getViewHeight(); } /** * Obter a altura do headView */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * Obter a altura da instância atual de SRefreshLayout */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("Encontrado listView"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY() - mStartY; Logs.v("space:" + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("拦截成功"); return true; } else { Logs.v("不拦截"); return false; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) mLastY = ev.getRawY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //记录起始高度 mLastY = ev.getRawY();//记录按下时的Y坐标 break; //手指离开屏幕时 case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break; case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY(); - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//Reduzir a distância de movimento em uma certa proporção updateHeight(); break; } return super.onTouchEvent(ev); } private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_NORMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_NORMAL: mHeaderView.setVisiableHeight(0); break; } } private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //Atualizar a altura da instância atual do layout para a altura do headerView mais a altura inicial do layout //Se não atualizar o layout, comprimirá a altura do conteúdo, impossível manter a proporção lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //Se não estiver em refresh e se a altura if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); } public void stopRefresh() { if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); resetHeadView(SRefreshHeader.STATE_NORMAL); reset(SRefreshHeader.STATE_NORMAL); } } public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.mOnRefreshListener = onRefreshListener; } }
public class SRefreshHeader extends LinearLayout { private LinearLayout mContainer; private int mState = STATE_NORMAL; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private final int ROTATE_ANIM_DURATION = 500; public final static int STATE_NORMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//释放刷新 public final static int STATE_REFRESHING = 2;//正在刷新 private ImageView mHeadArrowImage; private TextView mHeadLastRefreashTimeTxt; private TextView mHeadHintTxt; private TextView mHeadLastRefreashTxt; private ProgressBar mRefreshingProgress; public SRefreshHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public SRefreshHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { // Em situação inicial, definir a altura da view de atualização下拉 para 0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow); mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time); mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text); mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt); mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // Mostrar progresso mHeadArrowImage.clearAnimation(); mHeadArrowImage.setVisibility(View.INVISIBLE); mRefreshingProgress.setVisibility(View.VISIBLE); } else { // Mostrar imagem de seta mHeadArrowImage.setVisibility(View.VISIBLE); mRefreshingProgress.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) { mHeadArrowImage.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mHeadArrowImage.clearAnimation(); } mHeadHintTxt.setText("Recarregar"); break; case STATE_READY: if (mState != STATE_READY) { mHeadArrowImage.clearAnimation(); mHeadArrowImage.startAnimation(mRotateUpAnim); mHeadHintTxt.setText("solte para atualizar"); } break; case STATE_REFRESHING: mHeadHintTxt.setText("está atualizando"); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getStatus() { return mState; } public int getVisiableHeight() { return mContainer.getHeight(); } }
último é o arquivo de layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom"> <RelativeLayout android:id="@"+id/slistview_header_content" android:layout_width="match_parent" android:layout_height="60dp"> <LinearLayout android:id="@"+id/slistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <TextView android:id="@"+id/slistview_header_hint_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="arraste para atualizar" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp"> <TextView android:id="@"+id/slistview_header_last_refreash_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="última atualização" android:textSize="12sp" /> <TextView android:id="@"+id/slistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <ProgressBar android:id="@"+id/slistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:visibility="invisible" /> <ImageView android:id="@"+id/slistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/slistview_header_progressbar" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:src="@drawable/mmtlistview_arrow" /> </RelativeLayout> </LinearLayout>
Isso é tudo o que há no artigo. Esperamos que isso ajude no seu aprendizado e que você apoie o Tutorial Grito.
Declaração: O conteúdo deste artigo é de origem na internet, pertence ao respectivo proprietário, foi submetido e carregado voluntariamente pelos usuários da internet. Este site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidade por eventuais responsabilidades legais. 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. Caso seja confirmada, o site deletará imediatamente o conteúdo suspeito de violação de direitos autorais.)