2016-08-12 45 views
2

我創建了只擴展View類的自定義視圖。自定義視圖完美工作,除非在RecyclerView中使用。這是自定義視圖:如何製作與RecyclerView兼容的Android自定義視圖

public class KdaBar extends View { 
    private int mKillCount, mDeathCount, mAssistCount; 
    private int mKillColor, mDeathColor, mAssistColor; 
    private int mViewWidth, mViewHeight; 
    private Paint mKillBarPaint, mDeathBarPaint, mAssistBarPaint, mBgPaint; 
    private float mKillPart, mDeathPart, mAssistPart; 

    public KdaBar(Context context, AttributeSet attrs) { 
     super(context, attrs); 

     TypedArray a = context.getTheme().obtainStyledAttributes(
       attrs, 
       R.styleable.KdaBar, 
       0, 0); 

     try { 
      mKillCount = a.getInt(R.styleable.KdaBar_killCount, 0); 
      mDeathCount = a.getInt(R.styleable.KdaBar_deathCount, 0); 
      mAssistCount = a.getInt(R.styleable.KdaBar_assistCount, 0); 

      mKillColor = a.getColor(R.styleable.KdaBar_killBarColor, ContextCompat.getColor(getContext(), R.color.kill_score_color)); 
      mDeathColor = a.getColor(R.styleable.KdaBar_deathBarColor, ContextCompat.getColor(getContext(), R.color.death_score_color)); 
      mAssistColor = a.getColor(R.styleable.KdaBar_assistBarColor, ContextCompat.getColor(getContext(), R.color.assist_score_color)); 
     } finally { 
      a.recycle(); 
     } 

     init(); 
    } 

    public void setValues(int killCount, int deathCount, int assistCount) { 

     mKillCount = killCount; 
     mDeathCount = deathCount; 
     mAssistCount = assistCount; 

     invalidate(); 
    } 

    @Override 
    public void onDraw(Canvas canvas) { 
     super.onDraw(canvas); 

     canvas.drawRect(0f, 0f, mViewWidth, mViewHeight, mBgPaint); 
     canvas.drawRect(mKillPart+mDeathPart, 0f, mKillPart+mDeathPart+mAssistPart, mViewHeight, mAssistBarPaint); 
     canvas.drawRect(mKillPart, 0f, mKillPart+mDeathPart, mViewHeight, mDeathBarPaint); 
     canvas.drawRect(0f, 0f, mKillPart, mViewHeight, mKillBarPaint); 
    } 

    @Override 
    protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){ 
     super.onSizeChanged(xNew, yNew, xOld, yOld); 

     mViewWidth = xNew; 
     mViewHeight = yNew; 

     float total = mKillCount + mDeathCount + mAssistCount; 
     mKillPart = (mKillCount/total) * mViewWidth; 
     mDeathPart = (mDeathCount/total) * mViewWidth; 
     mAssistPart = (mAssistCount/total) * mViewWidth; 
    } 

    private void init() { 
     mKillBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mKillBarPaint.setColor(mKillColor); 

     mDeathBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mDeathBarPaint.setColor(mDeathColor); 

     mAssistBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mAssistBarPaint.setColor(mAssistColor); 

     mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mBgPaint.setColor(ContextCompat.getColor(getContext(), R.color.transparent)); 
    } 
} 

鏈接的圖像就是自定義視圖現在看起來像(自定義視圖爲中心的數字上面的矩形)http://imgur.com/a/Ib5Yl

的數字低於條代表其價值(如果你沒有注意到它們是顏色編碼的)。很顯然,第一個項目上的零值不應在自定義視圖上顯示藍條。奇怪,我知道。

下面的方法是其中所述值被設置(它是RecyclerView.Adapter <>內):

@Override 
public void onBindViewHolder(ViewHolder holder, int position) { 
    MatchHistory.Match item = mDataset.get(position); 
    MatchHistory.MatchPlayer[] players = item.getPlayers(); 

    for(MatchHistory.MatchPlayer player: players) { 
     int steamId32 = (int) Long.parseLong(mCurrentPlayer.getSteamId()); 
     if (steamId32 == player.getAccountId()) { 
      mCurrentMatchPlayer = player; 
     } 
    } 
    ... 
    holder.mKdaBar.setValues(mCurrentMatchPlayer.getKills(), mCurrentMatchPlayer.getDeaths(), mCurrentMatchPlayer.getAssists()); 
    ... 
} 

這是onCreateViewHolder:

@Override 
public MatchesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_match_item, parent, false); 
    ViewHolder vh = new ViewHolder(v); 
    return vh; 
} 

和ViewHolder類:

public static class ViewHolder extends RecyclerView.ViewHolder { 
    KdaBar mKdaBar; 

    public ViewHolder(View v) { 
     super(v); 
     ... 
     mKdaBar = (KdaBar) v.findViewById(R.id.kda_bar); 
     ... 
    } 
} 

我認爲值得注意的是數據集是適配器使用時不時地改變項目的位置(因爲它正在同時被全部取出,但被插入以便數據集被排序)。我幾乎忘記了我還測試了不改變數據集內項目的位置,但仍然沒有任何好的結果。如果您檢查了圖片,您可以看到項目中還有其他信息,並且我100%確定這些信息都是正確的,但自定義視圖中的數據除外。

我在想,我忘記了一些必須重寫的方法,但我已經看了很多教程,並沒有提到這個問題。期待解決這個問題。 TIA!

+0

你能分享更多的代碼嗎?自定義視圖只是畫布繪圖嗎?你如何設置mCurrentMatchPlayer?什麼是onCreateViewHolder工作 – napkinsterror

+0

@napkinsterror是自定義視圖只是畫布繪圖,對於mCurrentMatchPlayer和onCreateViewHolder,請檢出編輯過的帖子。 –

回答

1

的問題是不是與數據集,但與我的RecyclerView是如何工作的下面的理解(就像napkinsterror提到在他的回答中)。

這一點,修改後的自定義視圖:

public class KdaBar extends View { 
    private int mKillCount, mDeathCount, mAssistCount; 
    private int mKillColor, mDeathColor, mAssistColor; 
    private int mViewWidth, mViewHeight; 
    private Paint mKillBarPaint, mDeathBarPaint, mAssistBarPaint, mBgPaint; 
    private float mKillPart, mDeathPart, mAssistPart; 

    public KdaBar(Context context, AttributeSet attrs) { 
     super(context, attrs); 

     TypedArray a = context.getTheme().obtainStyledAttributes(
       attrs, 
       R.styleable.KdaBar, 
       0, 0); 

     try { 
      mKillCount = a.getInt(R.styleable.KdaBar_killCount, 0); 
      mDeathCount = a.getInt(R.styleable.KdaBar_deathCount, 0); 
      mAssistCount = a.getInt(R.styleable.KdaBar_assistCount, 0); 

      mKillColor = a.getColor(R.styleable.KdaBar_killBarColor, ContextCompat.getColor(getContext(), R.color.kill_score_color)); 
      mDeathColor = a.getColor(R.styleable.KdaBar_deathBarColor, ContextCompat.getColor(getContext(), R.color.death_score_color)); 
      mAssistColor = a.getColor(R.styleable.KdaBar_assistBarColor, ContextCompat.getColor(getContext(), R.color.assist_score_color)); 
     } finally { 
      a.recycle(); 
     } 

     init(); 
    } 

    public void setValues(int killCount, int deathCount, int assistCount) { 
     mKillCount = killCount; 
     mDeathCount = deathCount; 
     mAssistCount = assistCount; 
    } 

    private void calculatePartitions() { 
     float total = mKillCount + mDeathCount + mAssistCount; 
     mKillPart = (mKillCount/total) * mViewWidth; 
     mDeathPart = (mDeathCount/total) * mViewWidth; 
     mAssistPart = (mAssistCount/total) * mViewWidth; 
    } 

    @Override 
    public void onDraw(Canvas canvas) { 
     super.onDraw(canvas); 

     calculatePartitions(); 

     canvas.drawRect(mKillPart+mDeathPart, 0f, mKillPart+mDeathPart+mAssistPart, mViewHeight, mAssistBarPaint); 
     canvas.drawRect(mKillPart, 0f, mKillPart+mDeathPart, mViewHeight, mDeathBarPaint); 
     canvas.drawRect(0f, 0f, mKillPart, mViewHeight, mKillBarPaint); 
    } 

    @Override 
    protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){ 
     super.onSizeChanged(xNew, yNew, xOld, yOld); 

     mViewWidth = xNew; 
     mViewHeight = yNew; 
    } 

    private void init() { 
     mKillBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mKillBarPaint.setColor(mKillColor); 

     mDeathBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mDeathBarPaint.setColor(mDeathColor); 

     mAssistBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mAssistBarPaint.setColor(mAssistColor); 

     mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
     mBgPaint.setColor(ContextCompat.getColor(getContext(), R.color.transparent)); 
    } 
} 

這是我所做的更改:

  1. 移除了setValues()invalidate()調用自onDraw()回調時調用父增加了視圖。
  2. mKillPart,mDeathPartmAssistPart的賦值移到calculatePartitions(),而這又被稱爲在onDraw()的內部。這是因爲計算所需的值在onDraw()內已完成。這將在下面解釋。

這是我從napkinsterror先生的回答雲集:

當LayoutManager的詢問RecyclerView的看法,最終的onBindViewHolder()方法被調用。在該方法中,數據綁定到視圖,因此調用setValues()

視圖返回到LayoutManager,然後將該項目添加回RecyclerView。此事件將觸發onSizeChanged(),因爲視圖的尺寸還未知。這就是檢索mViewWidthmViewHeight的地方。此時,calculatePartitions()的所有必需值都已完成。

onDraw()也被稱爲是因爲父母剛剛添加了一個項目(請檢查此image)。 calculatePartitions()onDraw()之內被調用,並且該視圖將在畫布上繪製而沒有任何問題。

的原因,我得到錯誤的價值觀,是因爲之前我做calculatePartitions()onSizeChanged()這是非常,非常錯誤的,因爲mViewWidthmViewHeight尚未得知。

我會將此標記爲答案,但非常感謝先生。 napkinsterror提供資源,使我可以在正確的方向進行研究。 :)

+0

做得非常好,很高興你自己回答。我覺得我的'答案'不是答案,而只是一些指針和調試提示。我不知道'onSizeChanged()'和'onDraw()'的一切。很高興你對它進行了調查,我們都瞭解它,最重要的是,你的代碼正在工作! – napkinsterror

2

很難分辨究竟發生了什麼,特別是如果這段代碼在其他地方工作,但我會採取一些猜測。

主要的事情,我注意到:

  1. ,其中數字是危險地接近最大
  2. 從RecyclerView(尤其是onBindView)
內觀呼喚 的Invalidate INT從長期比較

第1期

在你的照片中,我猜你是SteamId,它們是每個RecyclerView視圖持有者左下角的數字,例如:'2563966339'。您應該知道Android中的「通常」,Integer.MAX_VALUE = 2147483647。這幾乎意味着你應該使用long或者當你認爲它們是不同的時候事物是不相等的...... (所以也許盒子被正確繪製,但你不認爲位置0處的steamId是你認爲?!?!)。

(如果你想了解更多關於它的信息,只需查看簽名和int字符串和長字符)。

所以你可能不得不改變一些代碼,但我建議使用長或長。許多可能性下面

例1

long steamId32 = Long.parseLong(mCurrentPlayer.getSteamId()); 
if (steamId32 == player.getAccountId()) { 
    mCurrentMatchPlayer = player; 
} 

例的兩個2

Long steamId32 = mCurrentPlayer.getSteamId(); 
if (steamId32.equals(player.getAccountId()) { 
    mCurrentMatchPlayer = player; 
} 

問題2:

的RecyclerView如何運作缺乏瞭解,可能是造成一些問題。在onBindView中,您應儘可能設置並繪製視圖(不要致電invalidate())。這是因爲RecyclerView是爲了處理所有'回收'。所以你無效()調用可能會導致一些奇怪的問題。

我知道onDraw()通常不會在每次視圖綁定時調用,但只有在使用RecyclerView創建時纔會調用。這將解釋爲什麼它在別處工作!

總結和分析:

數1:

我會打電話(內onBindViewsetValues之前)

Log.d("Whatever", "At position: " + position + " we have " + <steamId> + <kills> + <other desired info>)

上下滾動後,您將看到頂部的人員和正在調用的值,並查看它是否是#1中提到的問題或您的位置存在問題。如果該人應該有0,那麼讓位置0顯示0殺。

這也可以指出的這些問題,我不認爲是爲可能的,但絕對有可能:

我仍然不知道mCurrentPlayer到底是什麼,可能導致一個問題。此外,如果您需要更新適配器中的「項目」,只需使用recyclerView從Activity/Fragment中調用mAdapter.updateItemAt(position)即可。如果你不得不移動它,請致電mAdapter.notifyItemMoved(fromPos, toPos)。所有這些都意味着,當onBindView被調用時,事情可能不是你想象的。

數2:

我建議把日誌語句也onDraw(),看看你知道什麼時候是ACTUALLY被調用,而不是僅僅指望它invalidate()後。最有可能的是invaidate()正在排隊由主線程/回收者視圖,直到它決定它想要調用onDraw()

(因爲它已經在created/drewonCreateView()項目)

你可能會由什麼RecyclerView,佈局管理和適配器做感到驚訝,以及它們如何調用視圖的方法。(您也可能只想將onBindViewonCreateView中的Log語句理解爲onDraw()的整個過程)。

瞭解RecyclerView(和它的部分)

視頻,瞭解基礎知識:

而對於讀者來說,Android文檔提供本摘要:

Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set. 
Position: The position of a data item within an Adapter. 
Index: The index of an attached child view as used in a call to getChildAt(int). Contrast with Position. 
Binding: The process of preparing a child view to display data corresponding to a position within the adapter. 
Recycle (view): A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction. 
Scrap (view): A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty. 
Dirty (view): A child view that must be rebound by the adapter before being displayed. 
+0

1.將SteamId從長整型轉換爲整數是因爲期望值包含在數字的最低32位中,但我同意這在某種程度上是不安全的,我會研究這一點。除此之外,我確信這些價值觀是正確的,並處於他們需要的位置。 2.關於RecyclerView的工作原理,您所說的幾乎一半的東西對我來說都是很新穎的。感謝這些提示,我一定會嘗試這些。如果有任何進展/改進,將會更新。 另外,如果它真的有效,我會將其標記爲答案。謝謝你,先生:) –

+0

已更新,使其更清楚的數字1和數字2.絕對讀'onBindView'和'onCreateView'如何工作。此外,您可能想要查看是否可以在回收站視圖內的畫布上找到其他人的其他示例。我有一種感覺,它不會調用'invalidate()'。祝你好運。 – napkinsterror

相關問題