ListView에서 단일 행을 어떻게 업데이트합니까?


131

내가 가지고 ListView있는 디스플레이 뉴스 항목을. 여기에는 이미지, 제목 및 일부 텍스트가 포함됩니다. 이미지는 별도의 스레드 (대기열 및 모두 포함)에로드되고 이미지를 다운로드 할 때 notifyDataSetChanged()목록 어댑터를 호출 하여 이미지를 업데이트합니다. 이것은 작동하지만 모든 보이는 항목을 호출 하기 getView()때문에 너무 자주 notifyDataSetChanged()호출 getView()됩니다. 목록에서 단일 항목 만 업데이트하고 싶습니다. 어떻게해야합니까?

현재 접근 방식의 문제점은 다음과 같습니다.

  1. 스크롤이 느립니다
  2. 이미지에 페이드 인 애니메이션이 있습니다. 목록의 단일 새 이미지가로드 될 때마다 발생합니다.

답변:


199

귀하의 정보 Michelle 덕분에 답을 찾았습니다. 를 사용하여 올바른 뷰를 얻을 수 있습니다 View#getChildAt(int index). 캐치는 첫 번째 보이는 항목부터 계산을 시작한다는 것입니다. 실제로 보이는 항목 만 얻을 수 있습니다. 당신은 이것을 해결합니다 ListView#getFirstVisiblePosition().

예:

private void updateView(int index){
    View v = yourListView.getChildAt(index - 
        yourListView.getFirstVisiblePosition());

    if(v == null)
       return;

    TextView someText = (TextView) v.findViewById(R.id.sometextview);
    someText.setText("Hi! I updated you manually!");
}

2
자체 참고 사항 : mImgView.setImageURI ()는 목록 항목을 한 번만 업데이트합니다. 따라서 대신 mLstVwAdapter.notifyDataSetInvalidated ()를 사용하는 것이 좋습니다.
kellogs

이 솔루션이 작동합니다. 그러나 이것은 yourListView.getChildAt (index); 보기를 변경하십시오. 두 솔루션 모두 작동합니다 !!
안드로이드 Dev

위치가 getFirstVisiblePosition ()과 getLastVisiblePosition () 사이에있는 경우에만 어댑터에서 getView ()를 호출하면 어떻게됩니까? 동일하게 작동합니까? 평소처럼보기를 업데이트 할 것이라고 생각합니다.
Android 개발자

내 경우에는 각 항목이 비동기 적으로 업데이트되므로 view가 null 인 경우가 있습니다. view == null인지 확인하는 것이 좋습니다!
Khawar

2
잘 작동합니다. 인스 타 그램과 같은 앱에서 좋아요 기능을 구현하는 작업을 마치는 데 도움이됩니다. 목록 구성 요소 안에 like 버튼으로 방송 된 사용자 정의 어댑터를 사용하고 부모 조각에 방송 수신기를 사용하여 백엔드에 알리기 위해 asynctask를 시작했으며 마지막으로 목록을 업데이트하는 Erik의 대답입니다.
Josh

71

이 질문은 Google I / O 2010에서 요청되었으며 여기에서 볼 수 있습니다.

ListView의 세계, 시간 52:30

기본적으로 어떤 로맹 가이 설명하는 것은 호출하는 것입니다 getChildAt(int)ListView뷰와 (내가 생각하는) 호출을받을 getFirstVisiblePosition()위치와 지수 사이의 상관 관계를 알아.

Romain은 또한 Shelves 라는 프로젝트를 예로 들어 설명합니다. 그가 방법을 의미한다고 생각 ShelvesActivity.updateBookCovers()하지만의 전화를 찾을 수 없습니다 getFirstVisiblePosition().

멋진 업데이트가옵니다 :

RecyclerView는 가까운 시일 내에이 문제를 해결합니다. http://www.grokkingandroid.com/first-glance-androids-recyclerview/ 에서 지적했듯이 다음과 같이 변경 사항을 정확하게 지정하는 메소드를 호출 할 수 있습니다.

void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)

또한 모든 사람들은 RecyclerView를 기반으로 한 새로운 뷰를 사용하고 싶을 것입니다. 멋진 애니메이션으로 보답하기 때문입니다! 미래는 굉장해 보인다! :-)


3
좋은 것! :) 아마도 여기에 null 검사를 추가 할 수 있습니다-현재 뷰를 사용할 수 없으면 v가 null 일 수 있습니다. 물론 사용자가 ListView를 스크롤하면이 데이터가 사라 지므로 어댑터의 데이터도 업데이트해야합니다 (아마도 notifyDataSetChanged ()를 호출하지 않음). 일반적으로이 모든 논리를 어댑터 내에 유지하는 것이 좋습니다. 즉, ListView 참조를 전달하는 것이 좋습니다.
mreichelt

뷰가 화면을 떠났을 때, 다시 입력 할 때 변경이 재설정되는 것을 제외하고는 나를 위해 일했습니다. getview에서 여전히 원래 정보를 받고 있기 때문에 추측합니다. 이 문제를 어떻게 해결합니까?
gloscherrybomb

6

이것이 내가 한 방법입니다.

항목 (행)에는 고유 한 ID가 있어야 나중에 업데이트 할 수 있습니다. 목록이 어댑터에서보기를 가져올 때 모든보기의 태그를 설정하십시오. (기본 태그가 다른 곳에 사용 된 경우 키 태그를 사용할 수도 있습니다)

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    View view = super.getView(position, convertView, parent);
    view.setTag(getItemId(position));
    return view;
}

업데이트의 경우 목록의 모든 요소를 ​​확인하고 주어진 ID가있는보기가 있으면 표시되어 업데이트를 수행합니다.

private void update(long id)
{

    int c = list.getChildCount();
    for (int i = 0; i < c; i++)
    {
        View view = list.getChildAt(i);
        if ((Long)view.getTag() == id)
        {
            // update view
        }
    }
}

실제로 다른 방법보다 쉽고 위치가 아닌 ID를 처리 할 때 더 좋습니다! 또한 보이는 항목에 대해서는 update를 호출해야합니다.


3

이 모델 클래스 객체와 같이 모델 클래스를 전역으로 가져옵니다.

SampleModel golbalmodel=new SchedulerModel();

그리고 그것을 글로벌로 초기화하십시오

모델을 뷰로 초기화하여 뷰의 현재 행을 가져옵니다.

SampleModel data = (SchedulerModel) sampleList.get(position);
                golbalmodel=data;

변경된 값을 전역 모델 객체 메소드로 설정하고 notifyDataSetChanged를 추가하십시오.

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

여기에 좋은 답변이있는 관련 질문이 있습니다.


업데이트하려는 객체를 가져 와서 업데이트 한 다음 어댑터에 알리면 새 데이터로 행 정보가 변경되므로 올바른 방법입니다.
Gastón Saillén 2016 년

2

대답은 명확하고 정확 CursorAdapter합니다. 여기 에 사례 에 대한 아이디어를 추가 하겠습니다.

하위 클래스 CursorAdapter(또는 ResourceCursorAdapter, 또는 SimpleCursorAdapter) 인 경우 구현 ViewBinder하거나 재정의 bindView()하고 newView()메소드 를 가져 오는 경우 인수에서 현재 목록 항목 색인을받지 못합니다. 따라서 일부 데이터가 도착하고 관련 표시 목록 항목을 업데이트하려는 경우 해당 인덱스를 어떻게 알 수 있습니까?

내 해결 방법은 다음과 같습니다.

  • 작성된 모든 목록 항목보기 목록을 유지하고이 목록에 항목을 추가하십시오. newView()
  • 데이터가 도착하면 데이터를 반복하고 업데이트해야하는 데이터를 확인 notifyDatasetChanged()합니다.

뷰 재활용으로 인해 저장 해야하는 뷰 참조 수와 반복은 화면에 표시되는 목록 항목 수와 거의 같습니다.


2
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;

if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
    Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
    return;
}

View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);

mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);

RecycleView의 경우이 코드를 사용하십시오


나는 이것을 recycleView에 구현했는데 작동합니다. 그러나 목록을 스크롤하면 다시 변경됩니다. 어떤 도움?
Fahid Nadeem

1

Erik을 제공하는 코드를 사용했지만 훌륭하게 작동했지만 내 목록보기에 복잡한 사용자 정의 어댑터가 있으며 UI를 업데이트하는 코드의 두 번 구현에 직면했습니다. 어댑터 getView 메소드에서 새보기를 얻으려고 시도했습니다 (목록보기 데이터를 보유한 배열 목록이 이미 업데이트 / 변경되었습니다).

View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
    cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
    cell.startAnimation(animationLeftIn());
}

잘 작동하지만 이것이 좋은 습관인지는 모르겠습니다. 따라서 목록 항목을 두 번 업데이트하는 코드를 구현할 필요가 없습니다.


1

정확히 나는 이것을 사용했다.

private void updateSetTopState(int index) {
        View v = listview.getChildAt(index -
                listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());

        if(v == null)
            return;

        TextView aa = (TextView) v.findViewById(R.id.aa);
        aa.setVisibility(View.VISIBLE);
    }

상대 레이아웃 내에서 Textview를 찾는 방법을 상대 레이아웃으로보기를 받고 있습니까?
Girish

1

RecyclyerView 메서드 void 와 같은 다른 솔루션을 구성하여 다음과 같이 CustomBaseAdapter 클래스를 notifyItemChanged(int position)만듭니다 .

public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
    private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();

    public boolean hasStableIds() {
        return false;
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

    public void notifyItemChanged(int position) {
        mDataSetObservable.notifyItemChanged(position);
    }

    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEnabled(int position) {
        return true;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }

    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }

    public boolean isEmpty() {
        return getCount() == 0;
    } {

    }
}

다음 과 같이 CustomAdapterClass의 변수에 대해 CustomDataSetObservable 클래스 를 작성하는 것을 잊지 마십시오 .mDataSetObservable

public class CustomDataSetObservable extends Observable<DataSetObserver> {

    public void notifyChanged() {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }

    public void notifyItemChanged(int position) {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            mObservers.get(position).onChanged();
        }
    }
}

CustomBaseAdapter 클래스 에는 메소드가 있으며 notifyItemChanged(int position)버튼을 클릭하거나 해당 메소드를 호출하려는 위치에서 원하는 위치로 행을 업데이트 할 때 해당 메소드를 호출 할 수 있습니다. 그리고 짜잔!, 단일 행이 즉시 업데이트됩니다 ..


0

내 해결책 : 정확하면 * 전체 목록을 다시 그리지 않고 데이터와 볼 수있는 항목을 업데이트하십시오. 다른 notifyDataSetChanged.

올바른-oldData 크기 == 새 데이터 크기, 이전 데이터 ID 및 순서 == 새 데이터 ID 및 순서

어떻게:

/**
 * A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
 * is one-to-one and on.
 * 
 */
    private static class UniqueValueSparseArray extends SparseArray<View> {
    private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();

    @Override
    public void put(int key, View value) {
        final Integer previousKey = m_valueToKey.put(value,key);
        if(null != previousKey) {
            remove(previousKey);//re-mapping
        }
        super.put(key, value);
    }
}

@Override
public void setData(final List<? extends DBObject> data) {
    // TODO Implement 'smarter' logic, for replacing just part of the data?
    if (data == m_data) return;
    List<? extends DBObject> oldData = m_data;
    m_data = null == data ? Collections.EMPTY_LIST : data;
    if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
    else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}


/**
 * See if we can update the data within existing layout, without re-drawing the list.
 * @param oldData
 * @param newData
 * @return
 */
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
    /**
     * Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
     * items.
     */
    final int oldDataSize = oldData.size();
    if (oldDataSize != newData.size()) return false;
    DBObject newObj;

    int nVisibleViews = m_visibleViews.size();
    if(nVisibleViews == 0) return false;

    for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
        newObj = newData.get(position);
        if (oldData.get(position).getId() != newObj.getId()) return false;
        // iterate over visible objects and see if this ID is there.
        final View view = m_visibleViews.get(position);
        if (null != view) {
            // this position has a visible view, let's update it!
            bindView(position, view, false);
            nVisibleViews--;
        }
    }

    return true;
}

그리고 물론 :

@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
    final View result = createViewFromResource(position, convertView, parent);
    m_visibleViews.put(position, result);

    return result;
}

bindView의 마지막 매개 변수를 무시하십시오 (ImageDrawable의 비트 맵을 재활용해야하는지 여부를 판별하는 데 사용함).

위에서 언급했듯이 총 '표시'보기 수는 대략 화면에 맞는 크기 (방향 변경 등 무시)이므로 메모리 측면에서 더 큰 문제는 없습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.