Started implementation of Dashboard design pattern

This commit is contained in:
Dominik
2012-03-11 16:33:47 +01:00
parent 9ce7842ac9
commit e9c0d7a711
37 changed files with 18062 additions and 107 deletions

View File

@@ -8,7 +8,7 @@
android:versionName="1.1" >
<uses-sdk
android:minSdkVersion="4"
android:minSdkVersion="7"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.INTERNET" />
@@ -27,6 +27,7 @@
android:protectionLevel="dangerous" />
<application
android:hardwareAccelerated="true"
android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,19 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/background_noise_tile"
android:tileMode="repeat" />

View File

@@ -0,0 +1,28 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dashboard_encrypt_decrypt_file_pressed"
android:state_focused="true"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_encrypt_decrypt_file_pressed"
android:state_focused="false"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_encrypt_decrypt_file_pressed"
android:state_focused="true" />
<item android:drawable="@drawable/dashboard_encrypt_decrypt_file_default"
android:state_focused="false"
android:state_pressed="false" />
</selector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dashboard_encrypt_decrypt_text_pressed"
android:state_focused="true"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_encrypt_decrypt_text_pressed"
android:state_focused="false"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_encrypt_decrypt_text_pressed"
android:state_focused="true" />
<item android:drawable="@drawable/dashboard_encrypt_decrypt_text_default"
android:state_focused="false"
android:state_pressed="false" />
</selector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dashboard_help_pressed"
android:state_focused="true"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_help_pressed"
android:state_focused="false"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_help_pressed"
android:state_focused="true" />
<item android:drawable="@drawable/dashboard_help_default"
android:state_focused="false"
android:state_pressed="false" />
</selector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dashboard_manage_keys_pressed"
android:state_focused="true"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_manage_keys_pressed"
android:state_focused="false"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_manage_keys_pressed"
android:state_focused="true" />
<item android:drawable="@drawable/dashboard_manage_keys_default"
android:state_focused="false"
android:state_pressed="false" />
</selector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dashboard_my_keys_pressed"
android:state_focused="true"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_my_keys_pressed"
android:state_focused="false"
android:state_pressed="true" />
<item android:drawable="@drawable/dashboard_my_keys_pressed"
android:state_focused="true" />
<item android:drawable="@drawable/dashboard_my_keys_default"
android:state_focused="false"
android:state_pressed="false" />
</selector>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
<!--
Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,76 +15,103 @@
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="5dip"
android:fillViewport="true">
android:background="@color/background_primary"
android:orientation="vertical" >
<ScrollView
android:layout_marginTop="10dip"
<org.apg.ui.widget.DashboardLayout
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:fillViewport="true">
android:layout_height="fill_parent" >
<ListView
android:id="@+id/accounts"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<Button
android:id="@+id/dashboard_manage_keys"
style="@style/DashboardButton"
android:drawableTop="@drawable/dashboard_manage_keys"
android:onClick="manageKeysOnClick"
android:text="@string/dashboard_manage_keys" />
</ScrollView>
<Button
android:id="@+id/dashboard_my_keys"
style="@style/DashboardButton"
android:drawableTop="@drawable/dashboard_my_keys"
android:onClick="myKeysOnClick"
android:text="@string/dashboard_my_keys" />
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="@android:style/ButtonBar">
<Button
android:id="@+id/dashboard_encrypt_decrypt_file"
style="@style/DashboardButton"
android:drawableTop="@drawable/dashboard_encrypt_decrypt_file"
android:onClick="encryptDecryptFileOnClick"
android:text="@string/dashboard_encrypt_decrypt_file" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/dashboard_encrypt_decrypt_text"
style="@style/DashboardButton"
android:drawableTop="@drawable/dashboard_encrypt_decrypt_text"
android:onClick="encryptDecryptTextOnClick"
android:text="@string/dashboard_encrypt_decrypt_text" />
<Button
android:id="@+id/btn_encryptFile"
android:text="@string/btn_encryptFile"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent"/>
<Button
android:id="@+id/dashboard_help"
style="@style/DashboardButton"
android:drawableTop="@drawable/dashboard_help"
android:onClick="helpOnClick"
android:text="@string/dashboard_help" />
</org.apg.ui.widget.DashboardLayout>
<Button
android:id="@+id/btn_decryptFile"
android:text="@string/btn_decryptFile"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent"/>
<!-- <LinearLayout -->
<!-- style="@android:style/ButtonBar" -->
<!-- android:layout_width="fill_parent" -->
<!-- android:layout_height="wrap_content" -->
<!-- android:orientation="vertical" > -->
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!-- <LinearLayout -->
<!-- android:layout_width="fill_parent" -->
<!-- android:layout_height="wrap_content" -->
<!-- android:orientation="horizontal" > -->
<Button
android:id="@+id/btn_encryptMessage"
android:text="@string/btn_encryptMessage"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent"/>
<Button
android:id="@+id/btn_decryptMessage"
android:text="@string/btn_decryptMessage"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent"/>
<!-- <Button -->
<!-- android:id="@+id/btn_encryptFile" -->
<!-- android:layout_width="0dip" -->
<!-- android:layout_height="fill_parent" -->
<!-- android:layout_weight="1" -->
<!-- android:text="@string/btn_encryptFile" /> -->
</LinearLayout>
</LinearLayout>
<!-- <Button -->
<!-- android:id="@+id/btn_decryptFile" -->
<!-- android:layout_width="0dip" -->
<!-- android:layout_height="fill_parent" -->
<!-- android:layout_weight="1" -->
<!-- android:text="@string/btn_decryptFile" /> -->
<!-- </LinearLayout> -->
</LinearLayout>
<!-- <LinearLayout -->
<!-- android:layout_width="fill_parent" -->
<!-- android:layout_height="wrap_content" -->
<!-- android:orientation="horizontal" > -->
<!-- <Button -->
<!-- android:id="@+id/btn_encryptMessage" -->
<!-- android:layout_width="0dip" -->
<!-- android:layout_height="fill_parent" -->
<!-- android:layout_weight="1" -->
<!-- android:text="@string/btn_encryptMessage" /> -->
<!-- <Button -->
<!-- android:id="@+id/btn_decryptMessage" -->
<!-- android:layout_width="0dip" -->
<!-- android:layout_height="fill_parent" -->
<!-- android:layout_weight="1" -->
<!-- android:text="@string/btn_decryptMessage" /> -->
<!-- </LinearLayout> -->
<!-- </LinearLayout> -->
</LinearLayout>

View File

@@ -0,0 +1,82 @@
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- <color name="textColorIconOverlay">#fff</color> -->
<!-- <color name="textColorIconOverlayShadow">#000</color> -->
<!-- <color name="shortcut_overlay_text_background">#7f000000</color> -->
<!-- <color name="quickcontact_list_divider">#ffcdcdcd</color> -->
<!-- <color name="quickcontact_list_background">#ffe2e2e2</color> -->
<!-- <color name="quickcontact_tab_indicator">#ff33b5e6</color> -->
<color name="background_primary">#FFFFFF</color>
<color name="text_primary">#333</color>
<!-- <color name="background_social_updates">#ffeeeeee</color> -->
<!-- Color used for the letter in the A-Z section header -->
<color name="section_header_text_color">#ff999999</color>
<!-- Color of the text in the updates tab in the tab carousel on the contact detail page -->
<!-- <color name="detail_update_tab_text_color">#777777</color> -->
<!-- Color of the text describing an unconsumed missed call. -->
<!-- <color name="call_log_missed_call_highlight_color">#FF0000</color> -->
<!-- Color of the text describing an unconsumed voicemail. -->
<!-- <color name="call_log_voicemail_highlight_color">#33b5e5</color> -->
<!-- Colour background for the voicemail playback ui. -->
<!-- <color name="voicemail_playback_ui_background">@android:color/black</color> -->
<!-- Colour of voicemail progress bar to the right of position indicator. -->
<!-- <color name="voicemail_playback_seek_bar_yet_to_play">#44ffffff</color> -->
<!-- Colour of voicemail progress bar to the left of position indicator. -->
<!-- <color name="voicemail_playback_seek_bar_already_played">#ccffffff</color> -->
<!-- Colour of text that appears on the voicemail ui. -->
<!-- <color name="voicemail_playback_ui_text">#cc696969</color> -->
<!-- Color of the theme of the People app -->
<!-- <color name="people_app_theme_color">#33B5E5</color> -->
<!-- Secondary text color in the Phone app -->
<!-- <color name="dialtacts_secondary_text_color">#888888</color> -->
<!-- Colors in the contact browser list -->
<!-- <color name="contact_count_text_color">#AAAAAA</color> -->
<!-- Color of the text of the tab carousel in the contact details -->
<!-- <color name="detail_tab_carousel_tab_label_color">#EEEEEE</color> -->
<!-- Color of the text for buttons in the action bar -->
<!-- <color name="action_bar_button_text_color">#FFFFFF</color> -->
<!-- Color of the status message for starred contacts in the People app -->
<!-- <color name="people_contact_tile_status_color">#CCCCCC</color> -->
<!-- Color of the semi-transparent shadow box on contact tiles -->
<!-- <color name="contact_tile_shadow_box_color">#7F000000</color> -->
<!-- Color of the vertical stripe that goes on the left of a block quote inside a stream item -->
<!-- <color name="stream_item_stripe_color">#CCCCCC</color> -->
<!-- Color of image view placeholder. -->
<!-- <color name="image_placeholder">#DDDDDD</color> -->
</resources>

View File

@@ -46,7 +46,7 @@
<string name="title_importFromQRCode">Import from QR Code</string>
<string name="title_signKey">Sign Key</string>
<string name="title_about">About</string>
<!-- section_lowerCase: capitalized words, no punctuation -->
<string name="section_userIds">User IDs</string>
<string name="section_keys">Keys</string>
@@ -81,7 +81,6 @@
<!-- menu_lowerCase: capitalized words, no punctuation -->
<string name="menu_about">About</string>
<string name="menu_addAccount">Add GMail Account</string>
<string name="menu_deleteAccount">Delete Account</string>
<string name="menu_managePublicKeys">Manage Public Keys</string>
<string name="menu_manageSecretKeys">Manage Secret Keys</string>
@@ -311,13 +310,7 @@
<string name="fast">fast</string>
<string name="slow">slow</string>
<string name="very_slow">very slow</string>
<!-- about -->
<string name="about_description">Android Privacy Guard (APG) is a OpenPGP implementation for Android.</string>
<string name="about_license">License: Apache License 2.0</string>
<string name="about_version">Version:</string>
<string name="about_developer">Developer: Thialfihar (Main developer), Senecaso (QRCode, send key, sign key), Markus Doits (AIDL), Oliver Runge, Dominik Schürmann</string>
<!-- texts -->
<!-- "OI File Manager", "ASTRO", and "K-9 Mail" must not be translated in order for the links to the market to work. -->
<string name="text_help">Install K-9 Mail for the best integration, it supports APG for PGP/INLINE and lets you directly encrypt/decrypt emails.
@@ -326,4 +319,20 @@
\n\nYou can also add a GMail account in the main activity via \"Add Account\", which simplifies decrypting emails received there.
\n\nCheck out the option menus in the various activities to find more functions.</string>
<!-- APG Plus -->
<!-- about -->
<string name="about_description">Android Privacy Guard (APG) is a OpenPGP implementation for Android.</string>
<string name="about_license">License: Apache License 2.0</string>
<string name="about_version">Version:</string>
<string name="about_developer">Developer: Thialfihar (Main developer), Senecaso (QRCode, send key, sign key), Markus Doits (AIDL), Oliver Runge, Dominik Schürmann</string>
<!-- Dashboard -->
<string name="dashboard_manage_keys">Manage Keys</string>
<string name="dashboard_my_keys">My Keys</string>
<string name="dashboard_encrypt_decrypt_file">Encrypt File</string>
<string name="dashboard_encrypt_decrypt_text">Encrypt Text</string>
<string name="dashboard_help">Help</string>
</resources>

View File

@@ -27,4 +27,16 @@
<item name="android:src">@drawable/ic_btn_round_plus</item>
</style>
<style name="DashboardButton">
<item name="android:layout_gravity">center_vertical</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:drawablePadding">2dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:background">@android:color/transparent</item>
</style>
</resources>

View File

@@ -42,54 +42,47 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends BaseActivity {
static {
Security.addProvider(new BouncyCastleProvider());
}
public void manageKeysOnClick(View view) {
startActivity(new Intent(this, PublicKeyListActivity.class));
}
public void myKeysOnClick(View view) {
startActivity(new Intent(this, SecretKeyListActivity.class));
}
public void encryptDecryptFileOnClick(View view) {
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
intent.setAction(Apg.Intent.ENCRYPT_FILE);
startActivity(intent);
// intent.setAction(Apg.Intent.DECRYPT_FILE);
}
public void encryptDecryptTextOnClick(View view) {
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
intent.setAction(Apg.Intent.ENCRYPT);
startActivity(intent);
// intent.setAction(Apg.Intent.DECRYPT);
}
public void helpOnClick(View view) {
// TODO: implement help view
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button encryptMessageButton = (Button) findViewById(R.id.btn_encryptMessage);
Button decryptMessageButton = (Button) findViewById(R.id.btn_decryptMessage);
Button encryptFileButton = (Button) findViewById(R.id.btn_encryptFile);
Button decryptFileButton = (Button) findViewById(R.id.btn_decryptFile);
encryptMessageButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
intent.setAction(Apg.Intent.ENCRYPT);
startActivity(intent);
}
});
decryptMessageButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
intent.setAction(Apg.Intent.DECRYPT);
startActivity(intent);
}
});
encryptFileButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
intent.setAction(Apg.Intent.ENCRYPT_FILE);
startActivity(intent);
}
});
decryptFileButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
intent.setAction(Apg.Intent.DECRYPT_FILE);
startActivity(intent);
}
});
if (!mPreferences.hasSeenHelp()) {
showDialog(Id.dialog.help);
}
@@ -185,8 +178,6 @@ public class MainActivity extends BaseActivity {
android.R.drawable.ic_menu_manage);
menu.add(0, Id.menu.option.manage_secret_keys, 1, R.string.menu_manageSecretKeys).setIcon(
android.R.drawable.ic_menu_manage);
menu.add(1, Id.menu.option.create, 2, R.string.menu_addAccount).setIcon(
android.R.drawable.ic_menu_add);
menu.add(2, Id.menu.option.preferences, 3, R.string.menu_preferences).setIcon(
android.R.drawable.ic_menu_preferences);
menu.add(2, Id.menu.option.key_server, 4, R.string.menu_keyServer).setIcon(

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apg.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* Custom layout that arranges children in a grid-like manner, optimizing for even horizontal and
* vertical whitespace.
*/
public class DashboardLayout extends ViewGroup {
private static final int UNEVEN_GRID_PENALTY_MULTIPLIER = 10;
private static final int OVERLAP_PENALTY_MULTIPLIER = 10;
private int mMaxChildWidth = 0;
private int mMaxChildHeight = 0;
public DashboardLayout(Context context) {
super(context, null);
}
public DashboardLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public DashboardLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMaxChildWidth = 0;
mMaxChildHeight = 0;
// Measure once to find the maximum child size.
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth());
mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight());
}
// Measure again for each child to be exactly the same size.
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildWidth, MeasureSpec.EXACTLY);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildHeight, MeasureSpec.EXACTLY);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec),
resolveSize(mMaxChildHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
final int count = getChildCount();
// Calculate the number of visible children.
int visibleCount = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
++visibleCount;
}
if (visibleCount == 0) {
return;
}
// Calculate what number of rows and columns will optimize for even horizontal and
// vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on.
int bestSpaceDifference = Integer.MAX_VALUE;
int spaceDifference;
// Horizontal and vertical space between items
int hSpace = 0;
int vSpace = 0;
int cols = 1;
int rows;
while (true) {
rows = (visibleCount - 1) / cols + 1;
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
spaceDifference = Math.abs(vSpace - hSpace);
if (rows * cols != visibleCount) {
spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER;
}
if (hSpace < 0) {
spaceDifference *= OVERLAP_PENALTY_MULTIPLIER;
}
if (vSpace < 0) {
spaceDifference *= OVERLAP_PENALTY_MULTIPLIER;
}
if (spaceDifference < bestSpaceDifference) {
// Found a better whitespace squareness/ratio
bestSpaceDifference = spaceDifference;
// If we found a better whitespace squareness and there's only 1 row, this is
// the best we can do.
if (rows == 1) {
break;
}
} else {
// This is a worse whitespace ratio, use the previous value of cols and exit.
--cols;
rows = (visibleCount - 1) / cols + 1;
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
break;
}
++cols;
}
// Lay out children based on calculated best-fit number of rows and cols.
// If we chose a layout that has negative horizontal or vertical space, force it to zero.
hSpace = Math.max(0, hSpace);
vSpace = Math.max(0, vSpace);
// Re-use width/height variables to be child width/height.
width = (width - hSpace * (cols + 1)) / cols;
height = (height - vSpace * (rows + 1)) / rows;
int left, top;
int col, row;
int visibleIndex = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
row = visibleIndex / cols;
col = visibleIndex % cols;
left = hSpace * (col + 1) + width * col;
top = vSpace * (row + 1) + height * row;
child.layout(left, top, (hSpace == 0 && col == cols - 1) ? r : (left + width),
(vSpace == 0 && row == rows - 1) ? b : (top + height));
++visibleIndex;
}
}
}