Wczytywanie grafik z różnych źródeł w aplikacji mobilnej

Uprawnienia do odczytu plików

Od Androida 6.0 (API 23) musimy poprosić o zgodę również w kodzie (runtime permission), służy do tego metoda onRequestPermissionsResult(). Funkcja obsługuje odpowiedzi użytkownika na prośbę o uprawnienie (np. dostęp do pamięci urządzenia). W systemie Android od wersji 6.0 (API 23) niektóre uprawnienia zwane niebezpiecznymi (dangerous permissions) muszą być zatwierdzone przez użytkownika w czasie działania aplikacji, a nie tylko w AndroidManifest.xml.

Dlaczego musimy prosić o uprawnienia?

Wprowadzony model uprawnień ?runtime permissions?, oznacza, że:
aplikacje nie mogą domyślnie uzyskać dostępu do poufnych danych, takich jak zdjęcia, pliki, kontakty czy lokalizacja, użytkownik musi świadomie wyrazić zgodę, np. po kliknięciu przycisku w aplikacji.

To podejście zwiększa bezpieczeństwo i prywatność użytkownika.

W pliku AndroidManifest.xml dodaj nad sekcją <application> te uprawnienia:

Wskazówka:


<!--Uprawnienie do odczytu plików-->
<!-- Android 13+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Android 12 i niżej -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
A w sekcji <application> :

Wskazówka:


android:requestLegacyExternalStorage="true"
Ta linijka jest bardzo ważna, bowiem przywraca pełny dostęp do plików na Androidzie 10 (API 29) i wyżej. Bez tego Android domyślnie uruchamia Scoped Storage, czyli ograniczony dostęp do zasobów zewnętrznych - nawet z pozornie przyznanym uprawnieniem.

Fragment pliku manifestu

Wskazówka:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

<!--Uprawnienie do odczytu plików-->
<!-- Android 13+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Android 12 i niżej -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--uprawnienie do internetu-->
<uses-permission android:name="android.permission.INTERNET" />
    
    <application
        android:allowBackup="true"
        
        android:requestLegacyExternalStorage="true"
    </application>

</manifest>

Przejdź do głównego pliku MainActivity.kt i zaimportuj te biblioteki

Wskazówka:


import android.os.Environment
import java.io.File

Grafika w emulatorze

Aby przetestować zachowanie budowanej aplikacji w emulatorze należy wgrać kilka dowolnych plików graficznych rozszerzeniami png, jpg, bmp, webp itp. W rzeczywistym urządzeniu pliki graficzne są przechowywane między innymi w folderze DCIM/ Camera. Postaramy się wgrać tam kilka plików z dysku komputera.

Wgraj grafikę do emulatora

Wybierz podgląd dostępu do struktury katalogów uruchomionego emulatora View/ Tool Windows/ Device Explorer

Android Studio wczytywanie grafiki

Nie widac folderu Camera

Odśwież widok folderu, Wybierz Upload i skopiuj pliki jpg, png itp. Opcja Upload dostępna jest w menu prawego przycisku myszki po wskazaniu folderu. Android Studio folder Camera

Ścieżka w zależności od wersji emulatora może się nieznacznie różnić. Poniżej ścieżka na emulatorze Pixel 9 Pro API 36

Android Studio DCIM/ Camera

Można też posłużyć się aparatem z emulatora. Otwórz na emulatorze aplikację Aparat zrób kilka zdjęć z emulatora (będą to te same widoki). Wyłącz emulator i ponownie uruchom. Grafika powinna się załadować do ponownie uruchomionego emulatora.

Układ widoku

Przygotuj widok w układzie LinearLayout (vertical)

Android Studio wczytywanie grafiki LinearLayout

Kontrolkę ListView rozciągnij tak aby były widoczne co najemnie dwa elementy listy. Pozostałe parametry osadzanych kontrolek dobierz według własnego uznania.

Na tym etapie tworzenia projektu zdefiniujemy wszystkie zmienne wykorzystywane w rozwiązaniu

Wskazówka:


class MainActivity : AppCompatActivity() {
    //flaga kodu żądania- dowolna stala unikatowa wartość
    private val PROSBA_DOSTEPU_DCIM = 1001

    private lateinit var imageView: ImageView
    private lateinit var listView: ListView
    private lateinit var imageHint: TextView
    private lateinit var btZPrzegladarki: Button
    private lateinit var btDostepneZasoby: Button

    private val imagePaths = mutableListOf<String>()
    private val imageNames = mutableListOf<String>()

W trakcie kodowania kompilator poprosi i zasugeruje o dołączenie odpowiednich bibliotek. Zgódź się.

Przechodzimy do funkcji onCreate() i dodajemy poniższe linie kodu

Wskazówka:


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    setContentView(R.layout.activity_main)
    ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
        insets
    }

    imageView = findViewById(R.id.imageView)
    listView = findViewById(R.id.listView)
    imageHint= findViewById(R.id.imageHint)
    btZPrzegladarki = findViewById(R.id.btZPrzegladarki)
    btDostepneZasoby = findViewById(R.id.btDostepneZasoby)

Wczytanie zasobów graficznych do ListView aplikacji

Po przyznaniu praw dostępu do zasobów możemy wczytać pliki graficzne do kontrolki ListView. Pliki zostaną pobrane z folderu DCIM/ Camera. Tworzymy funkcję wczytującą nazwy plików do listy

Wskazówka:


//laduj nazwy plików graficznych do listy
private fun ladujImageDCIM() {

        // Pobranie katalogu DCIM zgodnie z systemem Android
        val dcimDir: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
        val cameraFolder = File(dcimDir, "Camera") // DCIM/Camera

        if (!cameraFolder.exists() || !cameraFolder.isDirectory) {
            Toast.makeText(this, "Folder DCIM/Camera nie istnieje", Toast.LENGTH_SHORT).show()
            return
        }

        val imageFiles = cameraFolder.listFiles { file ->
            file.isFile && file.extension.lowercase() in listOf("jpg", "jpeg", "png", "gif")
        }

        if (imageFiles.isNullOrEmpty()) {
            Toast.makeText(this, "Brak zdjęć w folderze DCIM/Camera", Toast.LENGTH_SHORT).show()
            return
        }

        imagePaths.clear()
        imageNames.clear()

        for (file in imageFiles) {
            imagePaths.add(file.absolutePath)
            imageNames.add(file.name)
        }

        val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, imageNames)
        listView.adapter = adapter
}

Jej automatyczne wywołanie wykonamy w zdarzeniu obsługi prośby o przydzielenie uprawnień.

Wskazówka:


// Obsługa wyniku prośby o uprawnienia
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    if (requestCode == PROSBA_DOSTEPU_DCIM && grantResults.isNotEmpty() &&
        grantResults[0] == PackageManager.PERMISSION_GRANTED
    ) {
        ladujImageDCIM()
    } else {
        Toast.makeText(this, "Brak zgody na odczyt plików", Toast.LENGTH_LONG).show()
    }
}

Obsłużenie zgody należy wywołać. I tu możemy rozszerzyć uniwersalność aplikacji na różne wersje API Androida .W zależności od poziomu wersji API przydzielenie uprawnień nieznacznie się różni. W do funkcji onCreate() wprowadź poniższy kod:

Wskazówka:


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.READ_MEDIA_IMAGES),
            PROSBA_DOSTEPU_DCIM
        )
    } else {
        ladujImageDCIM()
    }
} else {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
            PROSBA_DOSTEPU_DCIM
        )
    } else {
        ladujImageDCIM()
    }
}

Na tę chwilę aplikacja nic nie potrafi zrobić poza jednym. Po pierwszym uruchomieniu pojawi się monit z pytaniem o przydzielenie uprawnień. Wybierz: Zezwól na wszystko.

Android Studio przydzielenie uprawnień

Jeżeli przez pomyłkę wybierzesz inną opcje, odinstaluj aplikacje z emulator i skompiluj program jeszcze raz.

Lista rozwijalna z dostępną grafiką powinna się wypełnić nazwami plików graficznych. Patrz poniższy zrzut ekranu.

Android Studio wczytywanie grafiki listview

Teraz zostaje obsłużyć kliknięcie w element listy w celu załadowania grafiki do podglądu (kontrolki ImageView).

Wskazówka:


listView.setOnItemClickListener { _, _, indeks, _ ->
    val path = imagePaths[indeks]
    val uri = Uri.fromFile(File(path))
    imageView.setImageURI(uri)
}

Skompiluj aplikację i sprawdź działanie. Po kliknięciu w element listy wybrany obraz powinien zostać załadowany do podglądu.

Android Studio wczytywanie grafiki wybrany obraz

Powyższe rozwiązanie wynikające z dostępnych metod API Androida jest dość kłopotliwe. Dużo łatwiej jest to zrobić wykorzystując bibliotekę Glide. UWAGA! Użycie tej biblioteki nie zwalnia z przypisania uprawnień aplikacji i zgody użytkownika.

Wybór obrazu z mechanizmu udostępniania środowiska Android

W kolejnym kroku budowy aplikacji wykonamy metodę wczytywanie obrazu pobranego z dowolnego zasobu zapisanego w systemie urządzenia mobilnego z wykorzystaniem biblioteki Glide. Glide to biblioteka open-source dla Androida stworzona przez firmę Bump Technologies (obecnie rozwijana przez Google), która ułatwia ładowanie i wyświetlanie obrazów w aplikacjach. Jednym z pomysłów jest wczytani obrazu poprzez mechanizm udostępniania z jednej aplikacji do innej, w tym przypadku naszej.

Po co jest Glide?

  • Umożliwia pobieranie obrazów z internetu i umieszczanie ich w ImageView.
  • Potrafi też ładować obrazy z lokalnych plików, URI czy nawet zasobów w drawable.
  • Ma wbudowane buforowanie (caching) zapisuje obrazy w pamięci i na dysku, żeby ładowały się szybciej przy kolejnych wywołaniach.
  • Automatycznie skaluję i optymalizuje obraz tak, aby pasował do rozmiaru kontrolki (ImageView).
  • Obsługuje GIF-y i animacje.
  • Dlaczego używać Glide, a nie BitmapFactory lub HttpURLConnection?

    Bez Glide musiałbyś:

    Glide robi to wszystko za Ciebie jedną linią kodu:

    Wskazówka:

    
    Glide.with(context)
        .load(url)
        .into(imageView)
    

    Główne funkcje Glide:

    Dodanie biblioteki Glide

    Dodanie biblioteki Glide wymaga modyfikacji pliku bulid.gradle.kts (dla module). Otwórz ten plik i wprowadź poniższe zmiany

    Wskazówka:

    
    plugins {
        alias(libs.plugins.android.application)
        alias(libs.plugins.kotlin.android)
        id("kotlin-kapt") // <-- dodaj tę linię
    }
        
        
    dependencies {
        implementation("com.github.bumptech.glide:glide:4.16.0") // <-- dodaj tę linię
        kapt("com.github.bumptech.glide:compiler:4.16.0") // <-- dodaj tę linię
        implementation(libs.androidx.appcompat)
        implementation(libs.material)
        
        
    }
    

    Po dodaniu wybierz synchronizację z projektem- kliknij Sync Now

    A w pliku głównym MainActivity.kt zaimportuj bibliotekę

    Wskazówka:

    
    import com.bumptech.glide.Glide
    

    Aplikację rozszerzymy o dwie ikony przedstawiające błąd ładowania zasobu graficznego oraz ikonę reprezentującą obraz. Pobranie darmowych ikon wspieranych przez Google możesz skorzystać z:

    Google Material Icons darmowe ikony od Google

    https://fonts.google.com/icons

    Pobrane ikony zaimportuj do folderu res/ drawable

    Android Studio drawable import grafiki

    Otwórz plik manifestu i dodaj dwa filtry w sekcji <activity></activity>

    Wskazówka:

    
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" /> <!-- udostępnianie linków -->
    </intent-filter>
    
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="image/*" /> <!-- udostępnianie obrazów zapisanych lokalnie -->
    </intent-filter>
    

    Otwórz plik MainActivity.kt i dodaj poniższą funkcję

    Wskazówka:

    
    //wczytaj obraz poprzez mechanizm udostępnij np. z przeglądania galerii obrazów
    private fun wczytaj_z_Udostepnij(intent: Intent?) {
        if (intent == null) return
    
        when (intent.action) {
            Intent.ACTION_SEND -> {
                when (intent.type) {
                    "text/plain" -> { // Link do obrazu
                        val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
                        if (!sharedText.isNullOrEmpty()) {
                            Glide.with(this)
                                .load(sharedText)
                                .placeholder(R.drawable.image_256dp)
                                .error(R.drawable.error_256dp)
                                .into(imageView)
                            imageHint.visibility = View.GONE
                        }
                    }
                    else -> if (intent.type?.startsWith("image/") == true) { // Plik obrazu lokalny
                        val imageUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
                        if (imageUri != null) {
                            Glide.with(this)
                                .load(imageUri)
                                .placeholder(R.drawable.image_256dp)
                                .error(R.drawable.error_256dp)
                                .into(imageView)
                            imageHint.visibility = View.GONE
                        }
                    }
                }
            }
        }
    }
    

    Funkcję wywołaj w metodzie onCreate()

    Wskazówka:

    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        
        
        
        listView.setOnItemClickListener { _, _, indeks, _ ->
            val path = imagePaths[indeks]
            val uri = Uri.fromFile(File(path))
            imageView.setImageURI(uri)
            // Po wczytaniu obrazu schowaj udawanego hinta
            imageHint.visibility = View.GONE
        }
            // Sprawdzenie czy uruchomiono przez "Udostępnij"
        wczytaj_z_Udostepnij(intent)
    
    }
    

    Działanie tej funkcji umożliwi wczytanie obraz na przykład z przeglądanie strony www. W uruchomionym emulatorze zamknij tworzona aplikację. Uruchom przeglądarkę i wczytaj dowolną stronę. Kliknij prawym przyciskiem myszy na dowolnej grafice i z menu prawego przycisku myszki wybierz jak poniżej.

    Android Studio wczytywanie grafiki

    W kolejnym oknie dialogowym wybierz tworzoną aplikację

    Android Studio grafika

    System uruchomi tworzoną aplikację z wczytanym obrazem

    Android Studio zasoby graficzne

    Wczytanie obrazu z uruchomienia zewnętrznej aplikacji- wyszukiwarki Google Chrome

    Kolejna metoda wczytania obrazu będzie polegać na uruchomieniu wyszukiwarki Google Chrome z poziomu aplikacji, wyszukaniu obrazu i wybraniu metody Udostępnij.

    Do pliku MainActivity.kt dodajemy funkcję która uruchomi przeglądarkę Google Chrome

    Wskazówka:

    
    //uruchom przegladarke google chrom
    private fun otworzGoogleChrom() {
        val uri = Uri.parse("https://images.google.com/")
        val chromeIntent = Intent(Intent.ACTION_VIEW, uri).apply {
            setPackage("com.android.chrome")
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        startActivity(chromeIntent)
    }
    

    Funkcję wywołamy w metodzie onCreate() poprzez obsługe zdarzenia klikniecia w kontrolce Button

    Wskazówka:

    
    // Kliknięcie w przycisk wybierz z przegladarki
    btZPrzegladarki.setOnClickListener {
        otworzGoogleChrom()
    }
    

    Skompiluj program. Uruchom przeglądarkę z poziomu aplikacji. W pisz w pasek wyszukiwania : obrazy png. Przejdź na zakładkę Grafika.

    Android Studio metoda udostepnij

    Wskaż plik obrazu i wybierz Udostępnij

    Android Studio wczytywanie grafiki udostępnij

    W oknie dialogowym wybierz tworzoną aplikację

    Android Studio wczytywanie grafiki

    Obraz zostanie wczytany do naszej aplikacji

    Android Studio wczytywanie plików graficznych

    Wczytanie grafiki z dostępnych zasobów urządzenia mobilnego

    To rozwiązanie jest analogiczne do pierwszej metody, różni się szerszym dostępem do zasobów urządzenia mobilnego. Skorzystamy to nowego sposobu uruchamiania zewnętrznej akcji i odbierania jej wyniku w Androidzie, który zastępuje stare startActivityForResult() i onActivityResult().

    Utwórz poniższą funkcję

    Wskazówka:

    
    //laduj obraz z zasobów urządzenia mobilnego
    private val LadujObraz = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        if (uri != null) {
            // bezpośrednio wczytujemy do ImageView
            imageView.setImageURI(uri)
        } else {
            Toast.makeText(this, "Nie wybrano pliku", Toast.LENGTH_SHORT).show()
        }
    }
    

    Co oznacza = registerForActivityResult(...)

    registerForActivityResult rejestruje w Activity (lub Fragmencie) specjalny obiekt, który potrafi: >

    W tym kodzie kontraktem jest:

    ActivityResultContracts.GetContent()

    co oznacza: ?Otwórz systemowy selektor plików i pozwól użytkownikowi wybrać pojedynczy plik określonego typu MIME?.

    Dlaczego wywołuje się to tak:

  • LadujObraz.launch("image/*")
  • W skrócie:

    Funkcje wywołaj w zdarzeniu kliknięcia w kontrolkę Button

    Wskazówka:

    
    // Kliknięcie w przycisk wczytanie obrazu z dostepnych zasobów
    btDostepneZasoby.setOnClickListener {
        LadujObraz.launch("image/*")
    }
    

    Skompiluj aplikację i sprawdź efekt. Po wybraniu klawisza tej metody w emulatorze otwiera się okno podglądu zasobów graficznych

    Android Studio dostep do zasobów graficznych

    Teraz można wskazać obraz i załadować do aplikacji

    Pełny kod rozwiązania

    W kodzie źródłowym znajdziesz nieomawiany hint podpowiedzi widoczny na środku kontrolki ImageView. Ten fragment został dodany od tak sobie aby było ładniej. Nie wpływa na funkcjonowanie ładowania grafiki jest jedynie dodatkowym środkiem wizualnym. Hint jest utworzony w dodatkowym widoku (layout) zapisanym w pliku XML

    Plik res/ layout/ img_tekst_hint.xml

    Wskazówka:

    
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="320dp">
        <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter" />
    
        <TextView
            android:id="@+id/imageHint"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#33FFFFFF"
            android:gravity="center"
            android:text="@string/podglad_obrazu"
            android:textColor="#888888"
            android:textSize="18sp" />
    </FrameLayout>
    

    Plik res/ layout/ activity_main.xml

    Wskazówka:

    
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="top"
        tools:context=".MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:orientation="vertical"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">
    
    
            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="320dp">
    
                <ImageView
                    android:id="@+id/imageView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="fitCenter"
                    android:background="#C5EB98"
                    android:contentDescription="Obraz" />
    
                <TextView
                    android:id="@+id/imageHint"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center"
                    android:text="Tutaj pojawi się obraz"
                    android:textSize="18sp"
                    android:textColor="#888888"
                    android:background="@android:color/transparent" />
            </FrameLayout>
    
    
    
            <TextView
                android:id="@+id/textView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/kolekcja_grafiki"
                android:textSize="20sp" />
    
            <ListView
                android:id="@+id/listView"
                android:layout_width="match_parent"
                android:layout_height="174dp"
                android:background="#DCE0C2"
                android:dividerHeight="1dp"
                android:padding="20dp" />
    
            <Button
                android:id="@+id/btZPrzegladarki"
                android:layout_width="249dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="8dp"
                android:text="@string/wybierz_z_przegladarki" />
            <Button
                android:id="@+id/btDostepneZasoby"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="8dp"
                android:text="@string/wybierz_z_zasobów" />
    
        </LinearLayout>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Plik manifestu AndroidManifest.xml

    Wskazówka:

    
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <!--Uprawnienie do odczytu plików-->
        <!-- Android 13+ -->
        <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
        <!-- Android 12 i niżej -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    	<!--uprawnienie do internetu-->
    	<uses-permission android:name="android.permission.INTERNET" />
    
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.WczytywanieGrafiki"
            android:requestLegacyExternalStorage="true"
            >
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
    
                <intent-filter>
                    <action android:name="android.intent.action.SEND" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:mimeType="text/plain" /> <!-- udostępnianie linków -->
                </intent-filter>
    
                <intent-filter>
                    <action android:name="android.intent.action.SEND" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:mimeType="image/*" /> <!-- udostępnianie obrazów zapisanych lokalnie -->
                </intent-filter>
    
            </activity>
        </application>
    
    </manifest>
    

    Plik MainActivity.kt

    Wskazówka:

    
    import android.os.Bundle
    import androidx.activity.enableEdgeToEdge
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.view.ViewCompat
    import androidx.core.view.WindowInsetsCompat
    import android.Manifest
    import android.content.Intent
    import android.content.pm.PackageManager
    import android.net.Uri
    import android.os.Build
    import android.widget.*
    import androidx.core.app.ActivityCompat
    import androidx.core.content.ContextCompat
    import android.os.Environment
    import android.view.View
    import com.bumptech.glide.Glide
    import java.io.File
    import androidx.activity.result.contract.ActivityResultContracts
    
    class MainActivity : AppCompatActivity() {
        //flaga kodu żądania- dowolna stala unikatowa wartość
        private val PROSBA_DOSTEPU_DCIM = 1001
    
        private lateinit var imageView: ImageView
        private lateinit var listView: ListView
        private lateinit var imageHint: TextView
        private lateinit var btZPrzegladarki: Button
        private lateinit var btDostepneZasoby: Button
    
        private val imagePaths = mutableListOf<String>()
        private val imageNames = mutableListOf<String>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            enableEdgeToEdge()
            setContentView(R.layout.activity_main)
            ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
                val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
                v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
                insets
            }
    
            imageView = findViewById(R.id.imageView)
            listView = findViewById(R.id.listView)
            imageHint= findViewById(R.id.imageHint)
            btZPrzegladarki = findViewById(R.id.btZPrzegladarki)
            btDostepneZasoby = findViewById(R.id.btDostepneZasoby)
    
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
                    != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.READ_MEDIA_IMAGES),
                        PROSBA_DOSTEPU_DCIM
                    )
                } else {
                    ladujImageDCIM()
                }
            } else {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
                        PROSBA_DOSTEPU_DCIM
                    )
                } else {
                    ladujImageDCIM()
                }
            }
    
            listView.setOnItemClickListener { _, _, indeks, _ ->
                val path = imagePaths[indeks]
                val uri = Uri.fromFile(File(path))
                imageView.setImageURI(uri)
                // Po wczytaniu obrazu schowaj udawanego hinta
                imageHint.visibility = View.GONE
            }
            // Kliknięcie w przycisk wybierz z przegladarki
            btZPrzegladarki.setOnClickListener {
                otworzGoogleChrom()
            }
            // Kliknięcie w przycisk wczytanie obrazu z dostepnych zasobów
            btDostepneZasoby.setOnClickListener {
                LadujObraz.launch("image/*")
            }
    
            // Sprawdzenie czy uruchomiono przez "Udostępnij"
            wczytaj_z_Udostepnij(intent)
    
        }
        //laduj nazwy plików graficznych do listy
        private fun ladujImageDCIM() {
    
                // Pobranie katalogu DCIM zgodnie z systemem Android
                val dcimDir: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
                val cameraFolder = File(dcimDir, "Camera") // DCIM/Camera
    
                if (!cameraFolder.exists() || !cameraFolder.isDirectory) {
                    Toast.makeText(this, "Folder DCIM/Camera nie istnieje", Toast.LENGTH_SHORT).show()
                    return
                }
    
                val imageFiles = cameraFolder.listFiles { file ->
                    file.isFile && file.extension.lowercase() in listOf("jpg", "jpeg", "png", "gif")
                }
    
                if (imageFiles.isNullOrEmpty()) {
                    Toast.makeText(this, "Brak zdjęć w folderze DCIM/Camera", Toast.LENGTH_SHORT).show()
                    return
                }
    
                imagePaths.clear()
                imageNames.clear()
    
                for (file in imageFiles) {
                    imagePaths.add(file.absolutePath)
                    imageNames.add(file.name)
                }
    
                val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, imageNames)
                listView.adapter = adapter
        }
        //laduj obraz z zasobów urządzenia mobilnego
        private val LadujObraz = registerForActivityResult(
            ActivityResultContracts.GetContent()
        ) { uri ->
            if (uri != null) {
                // bezpośrednio wczytujemy do ImageView
                imageView.setImageURI(uri)
                imageHint.visibility = View.GONE
            } else {
                Toast.makeText(this, "Nie wybrano pliku", Toast.LENGTH_SHORT).show()
            }
        }
        //uruchom przegladarke google chrom
        private fun otworzGoogleChrom() {
            val uri = Uri.parse("https://images.google.com/")
            val chromeIntent = Intent(Intent.ACTION_VIEW, uri).apply {
                setPackage("com.android.chrome")
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
            startActivity(chromeIntent)
        }
        //wczytaj obraz poprzez mechanizm udostępnij np. z przeglądania galerii obrazów
        private fun wczytaj_z_Udostepnij(intent: Intent?) {
            if (intent == null) return
    
            when (intent.action) {
                Intent.ACTION_SEND -> {
                    when (intent.type) {
                        "text/plain" -> { // Link do obrazu
                            val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
                            if (!sharedText.isNullOrEmpty()) {
                                Glide.with(this)
                                    .load(sharedText)
                                    .placeholder(R.drawable.image_256dp)
                                    .error(R.drawable.error_256dp)
                                    .into(imageView)
                                imageHint.visibility = View.GONE
                            }
                        }
                        else -> if (intent.type?.startsWith("image/") == true) { // Plik obrazu lokalny
                            val imageUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
                            if (imageUri != null) {
                                Glide.with(this)
                                    .load(imageUri)
                                    .placeholder(R.drawable.image_256dp)
                                    .error(R.drawable.error_256dp)
                                    .into(imageView)
                                imageHint.visibility = View.GONE
                            }
                        }
                    }
                }
            }
        }
    
        // Obsługa wyniku prośby o uprawnienia
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    
            if (requestCode == PROSBA_DOSTEPU_DCIM && grantResults.isNotEmpty() &&
                grantResults[0] == PackageManager.PERMISSION_GRANTED
            ) {
                ladujImageDCIM()
            } else {
                Toast.makeText(this, "Brak zgody na odczyt plików", Toast.LENGTH_LONG).show()
            }
        }
    }
    

    Plik bulid.gradle.kts

    Wskazówka:

    
    plugins {
        alias(libs.plugins.android.application)
        alias(libs.plugins.kotlin.android)
        id("kotlin-kapt") // <-- dodaj tę linię
    }
    
    android {
        namespace = "pl.afizyka.wczytywaniegrafiki"
        compileSdk = 36
    
        defaultConfig {
            applicationId = "pl.afizyka.wczytywaniegrafiki"
            minSdk = 24
            targetSdk = 36
            versionCode = 1
            versionName = "1.0"
    
            testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                isMinifyEnabled = false
                proguardFiles(
                    getDefaultProguardFile("proguard-android-optimize.txt"),
                    "proguard-rules.pro"
                )
            }
        }
        compileOptions {
            sourceCompatibility = JavaVersion.VERSION_11
            targetCompatibility = JavaVersion.VERSION_11
        }
        kotlinOptions {
            jvmTarget = "11"
        }
    }
    
    dependencies {
        implementation("com.github.bumptech.glide:glide:4.16.0")
        kapt("com.github.bumptech.glide:compiler:4.16.0")
        implementation(libs.androidx.appcompat)
        implementation(libs.material)
        implementation(libs.androidx.activity)
        implementation(libs.androidx.constraintlayout)
        testImplementation(libs.junit)
        androidTestImplementation(libs.androidx.junit)
        androidTestImplementation(libs.androidx.espresso.core)
    }
    
    Układ okresowy- kod qr
    Układ okresowy

    Układ okresowy pierwiastków- darmowa aplikacja na Androida

    Pobierz ze sklepu Google Play
    Alkomat- wirtualny test kod qr
    Alkomat- wirtualny test

    Alkomat- darmowa aplikacja na Androida

    Pobierz ze sklepu Google Play
    Światłomierz fotograficzny kod qr
    Światłomierz fotograficzny

    Światłomierz fotograficzny- darmowa aplikacja na Androida

    Pobierz ze sklepu Google Play
    Taklarz- olinowanie stałe kod qr
    Olinowanie stałe- kalkulator średnic

    Olinowanie stałe- darmowa aplikacja na Androida

    Pobierz ze sklepu Google Play
    przepis na gogfry

    Przepis na gofry

    zobacz
    przepis na bitą śmietanę

    Przepis na bitą śmietanę

    zobacz