2016-08-12 55 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

相关问题