首页 文章

我的自定义视图不会保存它的状态

提问于
浏览
0

我使用以下代码创建了一个自定义视图(为简洁起见编辑):

public class StampView extends View {

    // View State items
    private String stampText, stampTimeStamp;
    private int stampIconId;
    private boolean stamped;

    public StampView(Context context, @Nullable AttributeSet attrs) {
        // REMOVED
    }

    /**
     *
     * See: http://trickyandroid.com/saving-android-view-state-correctly/
     *
     * @return Returns a Parcelable object containing the view's current dynamic
     * state, or null if there is nothing interesting to save.
     * @see #onRestoreInstanceState(Parcelable)
     * @see #saveHierarchyState(SparseArray)
     * @see #dispatchSaveInstanceState(SparseArray)
     * @see #setSaveEnabled(boolean)
     */
    @Nullable @Override protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);

        // We need to save 4 extra things to save state.
        // StampText string
        ss.stampText = stampText;
        // Icon ID int
        ss.stampIconId = stampIconId;

        // Timestamp string
        ss.stampTimestamp = stampTimeStamp;

        // Stamped boolean
        ss.stamped = stamped;

        // An example
        // ss.state = customState;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        // our custom saved state items
        setStampIcon(ss.stampIconId);
        setStampTitleText(ss.stampText);
        setStampTimeStampText(ss.stampTimestamp);
        setStamped(ss.stamped);
    }

    /**
     * Extending the BaseSavedState allows us to extend state saving and save our own custom state variables above.
     *
     **/
    static class SavedState extends BaseSavedState {
        int stampIconId;
        String stampText, stampTimestamp;
        boolean stamped;

        SavedState(Parcelable superState) {
            super(superState);
        }

        // Reading In from a Parcel
        private SavedState(Parcel in) {
            super(in);
            stampIconId = in.readInt();
            stampText = in.readString();
            stampTimestamp = in.readString();
            stamped = in.readByte() != 0;  //myBoolean == true if byte != 0
        }

        // Writing to a Parcel
        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            // I assume order matters, since we don't seem to be writing the key values
            out.writeInt(stampIconId);
            out.writeString(stampText);
            out.writeString(stampTimestamp);
            out.writeByte((byte) (stamped ? 1 : 0));     //if myBoolean == true, byte == 1
        }

        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //REMOVED
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        Rect bounds = new Rect(0,0,mWidth,mHeight);

        if(stamped){
            // Draw the stamp

        } else {
            // Draw a placeholder instead
        }
    }

    public void constructStampViewFromPOI(POI poi){
        //removed
    }

    private void setStampTitleText (String titleString){
        this.stampText = titleString;
    }

    private void setStampTimeStampText (String tsString){
        this.stampTimeStamp = tsString;
    }



    private void setStampIcon(int collectionIconId) {
        // removed
    }

    public void setStamped(boolean bool) {
        this.stamped = bool;
        this.invalidate();
    }
}

我的自定义视图相当简单,它可以绘制出自己的2个版本,具体取决于名为'stamped'的布尔值 . 您可以在draw方法中看到这一点 . 我将此自定义视图放在6个项目的RecyclerView中的Viewholder中 . 我的活动应该有以下布局:

Viewholder #1 - Stamp view (#1 data) Viewholder #2 - Placeholder view Viewholder #3 - Placeholder view Viewholder #4 - Placeholder view Viewholder #5 - Placeholder view Viewholder #6 - Stamped view (#6 data)

但是,如果我调试我的活动,我会看到我的自定义视图被抽取6次,对于recyclerview中的每个视图持有者一次,然后由于某种原因它再次抽取所有6次 . 在第二遍中,它使用查看者#6的数据混淆了Viewholder#5中StampView的状态 . 我最终得到了这个布局:

Viewholder #1 - Stamp view (#1 data) Viewholder #2 - Placeholder view Viewholder #3 - Placeholder view Viewholder #4 - Placeholder view Viewholder #5 - Stamped view (#6 data) Viewholder #6 - Stamped view (#6 data)

单击一个视图持有者将我带到一个DetailsActivity,如果我这样做然后返回我的RecyclerView我看到这个布局:

Viewholder #1 - Stamp view (#1 data) Viewholder #2 - Placeholder view Viewholder #3 - Placeholder view Viewholder #4 - Stamped view (#6 data) Viewholder #5 - Stamped view (#6 data) Viewholder #6 - Stamped view (#6 data)

如果我再这样做两次,我最终会得到这个混乱的布局:

Viewholder #1 - Stamp view (#1 data) Viewholder #2 - Stamped view (#6 data) Viewholder #3 - Stamped view (#6 data) Viewholder #4 - Stamped view (#6 data) Viewholder #5 - Stamped view (#6 data) Viewholder #6 - Stamped view (#6 data)

在我看来,当我在屏幕上有多个实例时,我的自定义视图不会保存它's state and/or confusing it' s状态 . 我发现this post提到了这类问题,并试图实现它's way of saving state, but I cannot get the onSaveInstanceState method to fire within my view. I'已经尝试了所有提到的内容,包括 setSaveEnabled(true); 在我的自定义视图和视图内部,我尝试为自定义视图的每个实例设置自定义ID . 没有任何效果 .

  • 有人可以向我解释我做错了什么吗?

  • 我是否认为这是一个省钱问题?

  • 我应该如何以及在哪里保存自定义视图状态?在视图内部或是活动应该保存我的视图状态,因为它是视图的一部分?


编辑:这是视图代码,如果它有用:

@Override
public void onBindViewHolder(POI_View_Holder holder, int position) {

    //Instead lets use the viewholder bind method to assign content
    holder.bind(mPOIslist.get(position), listener);
}

static class POI_View_Holder extends RecyclerView.ViewHolder {

        // Variables for the ViewHolder
        private TextView name;
        private TextView positionText;
        private StampView stampView;

        POI_View_Holder(View itemView) {
            super(itemView);
            name = itemView.findViewById(R.id.poi_nameTV);
            positionText= itemView.findViewById(R.id.poi_positionTV);
            stampView = itemView.findViewById(R.id.poi_StampView);

//            stampView.setSaveEnabled(true); // force state saving
//            stampView.setId(getPosition());
        }

        public void bind(final POI poi, final OnPOIClickListener listener){

            // Set POI information in viewHolder
            name.setText(poi.getName());
            positionText.setText(String.valueOf(poi.getCollectionPosition()));

            if(poi.isStamped()){
                stampView.setElevation(4);
                stampView.setTranslationZ(4);
                stampView.setClipToOutline(true);
                // stampView.invalidate();
                stampView.constructStampViewFromPOI(poi);
            } else {
                stampView.setElevation(0);
                stampView.setTranslationZ(0);
            }
            Log.d("ViewHolder-", "bind: method fired");
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View view) {
                    listener.OnPOIClick(poi);
                }
            });
        }
    }

EDIT2:这是我的活动保存状态代码:

protected void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);

    // Save layoutManager state
    mListState = mLayoutManager.onSaveInstanceState();
    state.putParcelable(LIST_STATE_KEY, mListState);

    mCollectionList = Parcels.wrap(mListOfPOIsInCollection);
    state.putParcelable(COLLECTION_ARRAYLIST_STATE_KEY,mCollectionList);
}

protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);

    // Retrieve list state and list/item positions
    if(state != null) {
        mListState = state.getParcelable(LIST_STATE_KEY);
        //mCollectionList = state.getParcelable(COLLECTION_ARRAYLIST_STATE_KEY);
        mListOfPOIsInCollection = Parcels.unwrap(state.getParcelable(COLLECTION_ARRAYLIST_STATE_KEY));
    }
}

@Override protected void onResume() {
    super.onResume();

    if (mListState != null) {
        mLayoutManager.onRestoreInstanceState(mListState);
    }
}

1 回答

  • 1

    View.onSaveInstanceState()View.onRestoreInstanceState(Parcelable) 是指系统在销毁视图时调用,然后随着活动重新创建,可能是在您旋转手机时 . RecyclerView 不会以任何方式使用它们 .

    这听起来像你至少有一个问题,可能有两个问题 .

    首先,在适配器的 onBindViewHolder() 方法中,您需要确保始终设置每个子视图的状态 . 人们经常会遇到问题,他们只会在某些时候设置子视图的状态,这会导致视图被回收时出现问题 . 例如:

    if (myItem.isStamped()) {
        holder.stampView.setStamped(true);
    }
    

    如果您的视图被回收并重新绑定到未标记的数据项,则会中断 . 因为只有在印章存在时才设置 holder.stampView 的状态,所以当印章不存在时,您不会清除它 . 相反,你应该写:

    if (myItem.isStamped()) {
        holder.stampView.setStamped(true);
    } else {
        holder.stampView.setStamped(false);
    }
    

    要不就

    holder.stampView.setStamped(myItem.isStamped());
    

    其次,听起来可能正确保存/恢复适配器_2376508 . 我可以't really speak to this since I don' t拥有您的任何活动代码,但您可以利用 Activity.onSaveInstanceState(Bundle)savedInstanceState 包传递给 onCreate() 来保存它 .

相关问题