English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Implementación de diseño de layout de actualización de Android RefreshLayout

En el proyecto se necesita la función de actualización deslizante, pero este View no es un控件 de ListView, necesita implementar esta función a través de ViewGroup, al principio busqué un poco en línea, no encontré uno especialmente adecuado, ni entendí bien el código, por lo que decidí escribir uno solo.

  Así que saqué el código fuente de XlistView y lo vi uno por uno, y luego comprendí aproximadamente el código fuente de XLisview, finalmente decidí hacerlo yo mismo

  Para ahorrar tiempo, headView aún utiliza HeadView de XListView, lo que ahorra mucho tiempo:(

  Actualizar deslizante, actualizar deslizante, sin duda primero es implementar la función de deslizamiento, al principio planeé usar extends ScrollView para implementarlo, porque hay efectos de deslizamiento listos para usar, pero finalmente renuncié por dos razones:

1、在ScrollView下只能有一个子控件View,虽然在Scroll下添加一个ViewGroup,然后讲headView动态添加进前面的ViewGroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!

2、在ScrollView内嵌ListView时会发生冲突,还需要去重写ListView。于是放弃换个思路!

 关于上面的原因1:动态添加headView进ScrollView的中GroupView中,可以在重写ScrollView的onViewAdded()方法,将初始化时解析的headView添加进子GroupView

@Override 
public void onViewAdded(View child) { 
  super.onViewAdded(child); 
  //因为headView要在最上面,最先想到的就是Vertical的LinearLayout 
  LinearLayout linearLayout = (LinearLayout) getChildAt(0); 
  linearLayout.addView(view, 0); 
} 

  换个思路,通过extends LinearLayout来实现吧!
先做准备工作,我们需要一个HeaderView以及要获取到HeaderView的高度,还有初始时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

mHeaderViewContent = (RelativeLayout)

mHeaderView.findViewById(R.id.slistview_header_content);

 这是解析headerView内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mHeaderView来获取高度,点进构造方法里可以看到如下代码

// En condiciones iniciales, establecer la altura del view de actualización descendente en 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); 

如果直接获取mHeaderView的高度,那肯定是0
getHeaderViewHeight();
getViewHeight();
分别是获取HeaderView的高度和Layout的初始高度

/** 
  * Obtener la altura del headView 
  */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  /** 
  * Obtener la altura actual 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); 
      } 
    }); 
  } 

准备工作完成了,接下来就是要进行下拉操作了
到这里,肯定一下就想到了onTouchEvent()方法,是的!现在就开始在这里施工

实现下拉总共会经历三个过程
ACTION_UP→ACTION_MOVE→ACTION_UP
En el evento ACTION_UP, es decir, cuando el dedo se presiona, lo que necesitamos hacer es registrar la coordenada cuando se presiona

switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        //registra la altura de inicio 
        mLastY = ev.getRawY();//registra la coordenada Y pulsada 
        break; 

Luego es el evento ACTION_MOVE, que es el más importante, porque los cambios en la altura de HeadView y Layout al desplazarse hacia abajo se realizan aquí

case MotionEvent.ACTION_MOVE: 
       if (!isRefreashing) 
         isRefreashing = true; 
       final float deltaY = ev.getRawY(); - mLastY; 
       mLastY = ev.getRawY(); 
       updateHeaderViewHeight(deltaY / 1.8f);//Reducir la distancia de desplazamiento en una proporción determinada 
       updateHeight(); 
       break; 

Dentro de ellos, updateHeaderViewHeight y updateHeight son métodos para cambiar la altura de HeaderView y Layout respectivamente

 private void updateHeight() { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    //Actualizar la altura de la instancia actual del layout a la altura del headerView más la altura inicial del layout 
    //Si no se actualiza el layout, se comprimirá la altura del contenido, lo que no permitirá mantener la proporción 
    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) { 
      //Si no se encuentra en el estado de actualización y si la 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()); 
  } 

Al actualizar la altura del Header, se determina si se ha alcanzado la distancia de refresco mediante la distancia de desplazamiento, en el código anterior, se ha establecido que al alcanzar el doble de la altura inicial de mHeaderView, se entra en el estado de 'liberar para refrescar', y si no se ha alcanzado, se mantiene en el estado de 'refrescar hacia abajo'
En HeaderView, se han establecido varios estados3Los respectivos son

public final static int STATE_NORMAL = 0;//Refrescar deslizando 
 public final static int STATE_READY = 1;//Soltar actualización 
 public final static int STATE_REFRESHING = 2;//Actualizando 

El método de actualizar la altura es el mismo para headerView y layout, es decir, se suma la distancia de desplazamiento a la altura original y se asigna nuevamente a headerView o layout

mHeaderView.setVisiableHeight((int) space 
               + mHeaderView.getVisiableHeight()); 

Por último, es el evento ACTION_UP, que es cuando el dedo se levanta de la pantalla, aquí debemos decidir el estado final de headerView según el estado actual de headerView!

case MotionEvent.ACTION_UP: 
        //al soltar 
        //evitar que se disparen eventos de clic 
        if (!isRefreashing) 
          break; 
        //si el estado de headView está en READY, significa que al soltar debe entrar en el estado REFRESHING 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
        } 
        //restablecer la altura actual de SrefreshLayout y headView según el estado 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//restablecer coordenadas 
        break; 

resetHeadView y reset son métodos para resetear la altura de headerView y layout respectivamente

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; 
    } 
  } 

La forma de implementación es también la misma. Según el estado, si está en el estado de refresco, el headerView debe mostrarse normalmente y su altura debe ser la inicial, si está en NORMAL, es decir, en el estado de 'refrescar hacia abajo', no se ha desencadenado el refresco, al resetear, el headerView debe ocultarse, es decir, la altura se resetea a 0

Aquí también se ha completado básicamente la operación de refresco hacia abajo, solo necesita agregar una interfaz de devolución de llamada para notificar

interface OnRefreshListener { 
    void onRefresh(); 
  } 
case MotionEvent.ACTION_UP: 
        //al soltar 
        //evitar que se disparen eventos de clic 
        if (!isRefreashing) 
          break; 
        //si el estado de headView está en READY, significa que al soltar debe entrar en el estado REFRESHING 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //restablecer la altura actual de SrefreshLayout y headView según el estado 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//restablecer coordenadas 
        break; 

Bueno, aquí se ha completado básicamente, pruebe el efecto. ¡Ajá, descubrí un problema! ¿Por qué este Layout no puede ejecutar el refresco hacia abajo cuando se empala ListView? Después de pensar un poco, debería ser un problema de distribución de eventos, y还需要处理事件的拦截!
En cuanto al manejo de interceptación de eventos, leí el blog de la gran figura Hongyang sobre la distribución de eventos de ViewGroup y Android-Ultra-Pull-To-En la parte de código fuente de Refresh, encontré la solución:

@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("Encontrando 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("intercepción exitosa"); 
          return true; 
        } else { 
          Logs.v("no se intercepta"); 
          return false; 
        } 
    } 
    return super.onInterceptTouchEvent(ev); 
  } 

Entre ellos

if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
Space es la distancia de desplazamiento; canScrollVertically() es una función que determina si ListView puede desplazarse verticalmente, el parámetro negativo representa el desplazamiento hacia arriba y el positivo hacia abajo, y el último es la posición del primer item visible de ListView

With the above event interception processing, a Viewgroup that meets the requirements mentioned at the beginning is also completed!

Below is the source code of Layout and HeaderView (the HeaderView directly used by XlistView) source code

public class SRefreshLayout extends LinearLayout { 
  private SRefreshHeader mHeaderView; 
  private RelativeLayout mHeaderViewContent; 
  private boolean isRefreashing; 
  private float mLastY = -1;//initial height when pressed 
  private int mHeaderViewHeight;//height of the headerView content 
  private int mHeight;//height of the 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(); 
  } 
  /** 
   * Obtener la altura del headView 
   */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  /** 
   * Obtener la altura actual 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("Encontrando 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("intercepción exitosa"); 
          return true; 
        } else { 
          Logs.v("no se intercepta"); 
          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: 
        //registra la altura de inicio 
        mLastY = ev.getRawY();//registra la coordenada Y pulsada 
        break; 
      //cuando el dedo sale de la pantalla 
      case MotionEvent.ACTION_UP: 
        //al soltar 
        //evitar que se disparen eventos de clic 
        if (!isRefreashing) 
          break; 
        //si el estado de headView está en READY, significa que al soltar debe entrar en el estado REFRESHING 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //restablecer la altura actual de SrefreshLayout y headView según el estado 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//restablecer coordenadas 
        break; 
      case MotionEvent.ACTION_MOVE: 
        if (!isRefreashing) 
          isRefreashing = true; 
        final float deltaY = ev.getRawY(); - mLastY; 
        mLastY = ev.getRawY(); 
        updateHeaderViewHeight(deltaY / 1.8f);//Reducir la distancia de desplazamiento en una proporción determinada 
        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(); 
    //Actualizar la altura de la instancia actual del layout a la altura del headerView más la altura inicial del layout 
    //Si no se actualiza el layout, se comprimirá la altura del contenido, lo que no permitirá mantener la proporción 
    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) { 
      //Si no se encuentra en el estado de actualización y si la 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;//Refrescar deslizando 
  public final static int STATE_READY = 1;//Soltar actualización 
  public final static int STATE_REFRESHING = 2;//Actualizando 
  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) { 
    // En condiciones iniciales, establecer la altura del view de actualización descendente en 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 progreso 
      mHeadArrowImage.clearAnimation(); 
      mHeadArrowImage.setVisibility(View.INVISIBLE); 
      mRefreshingProgress.setVisibility(View.VISIBLE); 
    } else {  // Mostrar imagen de flecha 
      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("Refrescar hacia abajo"); 
        break; 
      case STATE_READY: 
        if (mState != STATE_READY) { 
          mHeadArrowImage.clearAnimation(); 
          mHeadArrowImage.startAnimation(mRotateUpAnim); 
          mHeadHintTxt.setText("suelte para actualizar"); 
        } 
        break; 
      case STATE_REFRESHING: 
        mHeadHintTxt.setText("actualizando..."); 
        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(); 
  } 
} 

lo último es el archivo de diseño

<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="desplazar para actualizar" /> 
      <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 vez actualizada" 
          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> 

Esto es todo el contenido del artículo, espero que sea útil para su aprendizaje y que todos nos apoyen y alentemos el tutorial.

Declaración: el contenido de este artículo se ha obtenido de la red, es propiedad del autor original, el contenido se ha contribuido y subido por los usuarios de Internet de manera autónoma. Este sitio no posee los derechos de propiedad, no ha sido editado por humanos y no asume responsabilidad alguna por las responsabilidades legales. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, por favor reemplace # con @) para denunciar y proporcionar evidencia relevante. Una vez confirmado, este sitio eliminará inmediatamente el contenido sospechoso de infracción.

Te gustará