/** * Recursive function to find all viable candidate combinations for laying out the next three * rows. */ private void generateCombos(int imageIndex) { if (mCombosGenerated >= MAX_COMBOS) { // Consider a maximum of 30 combinations for any iteration of this. return; } if (mPartialCombo.size() == MAX_ROWS_PER_COMBO || imageIndex == mImageCount) { // If the partial combo contains max rows OR we are out of images, this combination // is complete. StringBuilder builder = null; if (DEBUG_COMBOS) { builder = new StringBuilder(); builder.append('['); builder.append(mPartialCombo.computeScore()); builder.append("] "); for (int i = 0; i < mPartialCombo.size(); i++) { if (i != 0) { builder.append(", "); } Row row = mPartialCombo.getRow(i); builder.append(row.startIndex()); builder.append(" - "); builder.append(row.startIndex() + row.size() - 1); } } // Increment number of combos that have been generated. mCombosGenerated++; float scoreDiff = mBestCombo == null ? -1f : mPartialCombo.computeScore() - mBestCombo.computeScore(); // Pseudo-randomly decide whether to select between combos of equal score. if (scoreDiff == 0f) { mSameScoreCount++; int randomInt = mRandom.nextInt(mSameScoreCount); scoreDiff = (randomInt == 0) ? -1 : 1; } else if (scoreDiff < 0f) { mSameScoreCount = 1; } if (scoreDiff < 0f) { // Found new best combo, so save it (as a clone, since the partial combo wil continue // to be modified during combo generation. mBestCombo = mPartialCombo.clone(); } if (DEBUG_COMBOS) { if (scoreDiff < 0f) { // Indicate in the log that the combo was selected as current best. builder.append(" ***"); } Log.d("DebugCombos", builder.toString()); } return; } // For the current partial combination, compute possibilities for the next row. We will // consider each row of ideal height, plus up to one overheight and one underheight row. Row row = new Row(imageIndex); int overHeightSize = 0; for (int i = imageIndex; i < mImageCount; i++) { // Add photo to next row under consideration. row.addImage(); if (row.getAspect() < mMinRowAspect) { overHeightSize++; } else { // Add this row to the current partial combination and calculate the next set of // possible rows. mPartialCombo.addRow(row.clone()); generateCombos(i + 1); mPartialCombo.removeRow(); if (row.getAspect() > mMaxRowAspect) { break; } } } if (overHeightSize > 0) { mPartialCombo.addRow(new Row(imageIndex, overHeightSize)); generateCombos(imageIndex + overHeightSize); mPartialCombo.removeRow(); } }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Fill any available width. int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); // Don't re-measure everything unless width changes. if (mMeasuredWidth != measuredWidth) { mMeasuredWidth = measuredWidth; // From the full list of images, repeatedly carve off individual rows. Rows will be // selected to ensure a minimal amount of cropping while maintaining the aspect ratio of // each individual bitmap. int imageIndex = 0; while (imageIndex < mImageCount) { // To determine the next row, lay out the next three rows in ideal fashion and take the // first row. // This is accomplished by calculating a number of possible combinations for the next three // rows // and scoring each combination. The first row of the highest scoring combination will be // accepted. Assert.assertTrue(mPartialCombo.size() == 0); mBestCombo = null; mCombosGenerated = 0; mSameScoreCount = 0; if (DEBUG_COMBOS) { Log.d("DebugCombos", "ROW #" + (mLayoutRows.size() + 1)); } generateCombos(imageIndex); // Add first row from highest scoring combo to the list of rows for layout. Row row = mBestCombo.getRow(0); mLayoutRows.add(row); // Skip past images that are already laid out. imageIndex += row.size(); } // Calculate total height by adding together height of all rows. mMeasuredHeight = 0; for (int i = 0; i < mLayoutRows.size(); i++) { Row row = mLayoutRows.get(i); row.measure(mMeasuredWidth); mMeasuredHeight += row.getMeasuredHeight(); mMeasuredHeight += mBorderSize; } } // Height may be constrained by container. int measuredHeight = mMeasuredHeight; switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST: measuredHeight = Math.min(mMeasuredHeight, MeasureSpec.getSize(heightMeasureSpec)); break; case MeasureSpec.EXACTLY: measuredHeight = MeasureSpec.getSize(heightMeasureSpec); break; } setMeasuredDimension(measuredWidth, measuredHeight); }