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í

Tento web bude tebe a tvůj počítač krmit piškotkami, jelikož a protože je to slušný web a jako takový ví, že je potřeba návštěvu řádně pohostit, aby se u nás cítila dobře. Užíváním tohoto webu potvrzuješ, že netrpíš mentální anorexií, nedržíš žádnou obskurní dietu a že můžeš piškotki do sebe cpát kdykoli a v jakémkoli množství. Více informací...