Patnáctka podruhé - načítání vlastních fotek
Za normálních okolností bych téma přístupu k fotkám uživatele shrnul krátkým odstavcem a odkazem na již existující stránky. Řešení jsem vygooglil několik. To, že se lišily implementací, se dalo ještě očekávat, ale zásadní problém byl v tom, že každé z nich de facto řešilo trochu jiné zadání. Proto zase zveřejním snippety. Tentokrát to nebude kompletní class, budou to pouze části zdrojáků.
Dialog (Activity) s výběrem fotek
Než se podíváme na snippet, musíme si upřesnit zadání, co vlastně chceme: dát uživateli na výběr všechny jeho fotky bez ohledu na to, jestli je má uložené v interní nebo externí storage.
public class H15 extends FragmentActivity implements IChooseImageDialogListener {
private static int RESULT_LOAD_IMG = 2;
private Gameboard gameboard;
private GameData gameData;
...
@Override
public void chooseCustomImage() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
String heading = getResources().getString(R.string.choose_custom_image);
startActivityForResult(Intent.createChooser(intent, heading), RESULT_LOAD_IMG);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == RESULT_LOAD_IMG && resultCode == RESULT_OK) {
Uri uri = data.getData();
gameData.setPicId(0);
gameData.setPicUri(uri.toString());
gameboard.setPicId(0);
gameboard.setPicUri(uri.toString(), true);
gameData.store(this);
...
}
}
...
}
Snippet je součástí Activity
s hrací plochou. Tu reprezentuje třída Gameboard
. Třída GameData
je jakýsi DAO objekt, který se stará o persistenci dat hry (nejen statistiky a nastavení, ale také aktuální stav rozehrané hry).
Dialog - Activity
s výběrem fotek je spuštěn v metodě chooseCustomImage()
- callback metoda rozhraní IChooseImageDialogListener
spuštěná při kliknutí na tlačítko "Vlastní..." v dialogu s výběrem obrázků. O implementaci metody se dá říct jen jedno: tohle splňuje naše požadované zadání.
Metoda onActivityResult(int requestCode, int resultCode, Intent data)
je obecná metoda pro zpracování výsledků poskytovaných jinými Activity. V případě výběru fotek je návratovou hodnotou Uri
vybrané fotky. To pak dále zpracovávám v jeho textové podobě, ať se dobře ukládá do persistence.
A ještě vysvětlení ke konstantě RESULT_LOAD_IMG
. Jedná se o requestID
, podle kterého se pak pozná, která akce právě skončila a vrátila výsledek. Jeho hodnota může být jakákoli, ale každá akce by měla mít svou unikátní hodnotu. Akce pro výsledek mohou být spouštěny z různých míst kódu, ale výsledek bude zpracovaný vždy v metodě onActivityResult(...)
. Ta pak podle tohoto requestID
(parametr requestCode
) pozná, ke které akci daný výsledek patří.
Vlastní načtení fotky
Když už máme Uri
vybrané fotky, můžeme ji načíst. Obecně používaný postup, který koloval Internetem, měl jednu potíž: od KitKatu výše přestal fungovat. Bylo tedy potřeba najít postup, který bude fungovat pokud možno všude.
public class Gameboard extends SurfaceView implements SurfaceHolder.Callback {
private ImageCache imageCache;
private int picId = 1;
private String picUri = "";
...
private void loadCustomPicture() {
try {
if(!loadCachedPicture(picUri)) {
Uri uri = Uri.parse(picUri);
InputStream is = getContext().getContentResolver().openInputStream(uri);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
is.close();
options.inSampleSize = ChooseImageDialogAdapter.calculateInSampleSize(options, CUSTOM_IMAGE_SIZE, CUSTOM_IMAGE_SIZE);
is = getContext().getContentResolver().openInputStream(uri);
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeStream(is, null, options);
is.close();
createTiles(bmp, picUri);
}
} catch(Throwable e) {
// this means a picture does not exist anymore - simply load a preloaded picture number one.
Toast.makeText(getContext(), R.string.custom_image_error, Toast.LENGTH_LONG).show();
picId = 1;
picUri = "";
loadPicture();
}
}
...
}
Snippet je součástí třídy Gameboard
. Vlastní načítání fotky provádí metoda loadCustomPicture()
. Ta pracuje s Uri
fotky a také s ImageCache
v podobě, jak již byla popsána. Fotka je do cache ukládán po jednotlivých kostkách a jako klíč je použito její Uri
. Načítání kostek z cache funguje na principu "všechno nebo nic" - pokud jedna z kostek v cache chybí, jsou vytvořeny všechny znovu.
Pokud tedy nejsou kostky načteny z cache, dojde k vlastnímu načtení fotky. Základní princip načítání je pomocí Uri
otevřít InputStream
a ten pak nechat zpracovat BitmapFactory
. Pokud je vám zdroják nějaký povědomý, ano, i zde byl použit princip zmenšování obrázků podle Android tutoriálu, který již jednou implementován byl. S tím rozdílem, že tentokrát nenačítám obrázek přes resourceID
, ale přes Uri
.
Pokud je fotka úspěšně načtena, je rozdělena na hrací kostky a ty jsou pak uloženy do cache. Pokud dojde při načítání k chybě, znamená to, že uživatel fotku zřejmě smazal. V takovém případě je místo fotky načten první z obrázků distribuovaných s aplikací a je zobrazen Toast
s upozorněním pro uživatele.
Postup byl testován na starších zařízeních, ale také na Lollipopu (díky Dave). Všude fungoval bez problémů, takže ho můžeme prohlásit za obecně funkční.
To je k aktuální verzi H15 všechno. Uvidíme, co přinese další vývoj.
Tagy: H15, Android, Java, Programování