Как подключить галерею PhotoSwipe в WebView android

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

  • Tutorial
Я не профессиональный разработчик, хотя и учился на программиста. Сейчас работаю системным администратором и планирую переходить в разработчики. Пишу для себя приложение, которое парсит один из популярных сайтов тематики IT и показывает статьи в нативном приложении android. Захотелось, чтобы все картинки в статье открывались в галерее, как в мобильном приложении habr.

Скорее всего, руководство получится простым, но мне хотется получить feedback и, скорее всего, кому-то это руководство окажется полезным.

Я попробовал несколько плагинов. Несколько не запустились, другие требуют JQuery, а это замедляет загрузку страницы в приложении. В итоге, я выбрал плагин PhotoSwipe. плагин не требует JQuery, работает быстро и почти ничего не весит.

Для начала нужно подумать где в приложении лучше разместить исходные файлы плагина. Сначала по неопытности я размещал файлы в папку raw, но лучше размещать их в папку assets. Правой кнопкой мыши по app→new → folder→Assets Folder

Разметка будет одно простое WebView:

Код

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></WebView>
</android.support.constraint.ConstraintLayout>

Дальше нужно скачать или клонировать репозиторий с PhotoSwipe (ссылка на сайте плагина) и скопировать все содержимое папки dist в assets (dist можно переименовать в PhotoSwipe. Еще можно создать файл style.css в папке css, чтобы изображения занимали всю ширину.

img {
  width: 100%;
  height: auto;
}

Для галереи нужно подключить стили, javascipt файлы и файл стилей для скина. Можно сформировать html:

Здесь собирается html для WebView

public String getHTML() {
        String header = "<!DOCTYPE HTML><HTML><head><title>test</title>";
        // Вызываем скрипт, который подготавливает страницу к включению
        // плагина после полной загрузки страниицы.
        String headerEnd = "</head><body onload="init();">";
        String footer = "</body></HTML>";
        String charset = "<meta charset="utf-8">";
        String style = "<link href="css/style.css" type="text/css" rel="stylesheet" />";
        String pscss = "<link rel="stylesheet" href="PhotoSwipe/photoswipe.css">";
        String psdscss = "<link rel="stylesheet" href="PhotoSwipe/default-skin/default-skin.css">";
        // Скрипт, который создает галерею после нажатия на изображение
        String script = "<script type="text/javascript" src="js/script.js"></script>";
        String psjs = "<script src="PhotoSwipe/photoswipe.js"></script> ";
        String psuijs = "<script src="PhotoSwipe/photoswipe-ui-default.min.js"></script>";
        StringBuilder stringBuilder = new StringBuilder();
        // Собираем head
        stringBuilder.append(header);
        stringBuilder.append(charset);
        stringBuilder.append(style);
        stringBuilder.append(pscss);
        stringBuilder.append(psdscss);
        stringBuilder.append(script);
        stringBuilder.append(psjs);
        stringBuilder.append(psuijs);
        stringBuilder.append(headerEnd);
        stringBuilder.append("<img src="https://ps.denko.me/images/linux1.jpg" />");
        stringBuilder.append("<img src="https://ps.denko.me/images/linux2.jpg" />");
        stringBuilder.append("<img src="https://ps.denko.me/images/linux3.jpeg" />");
        stringBuilder.append(getPhotoSwipeHTML());
        stringBuilder.append(footer);
        return stringBuilder.toString();

.

Чтобы плагин заработал, так же нужно вставить контейнер для галереи в исходники страницы. В документации написано, что это не простой плагин и нужно часть работы сделать самостоятельно.

Код желательно вставлять перед закрывающим тегом

Контейнер для галереи

public String getPhotoSwipeHTML() {
        return "<!-- Root element of PhotoSwipe. Must have class pswp. -->n" +
                "<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">n" +
                "n" +
                "    <!-- Background of PhotoSwipe. n" +
                "         It's a separate element as animating opacity is faster than rgba(). -->n" +
                "    <div class="pswp__bg"></div>n" +
                "n" +
                "    <!-- Slides wrapper with overflow:hidden. -->n" +
                "    <div class="pswp__scroll-wrap">n" +
                "n" +
                "        <!-- Container that holds slides. n" +
                "            PhotoSwipe keeps only 3 of them in the DOM to save memory.n" +
                "            Don't modify these 3 pswp__item elements, data is added later on. -->n" +
                "        <div class="pswp__container">n" +
                "            <div class="pswp__item"></div>n" +
                "            <div class="pswp__item"></div>n" +
                "            <div class="pswp__item"></div>n" +
                "        </div>n" +
                "n" +
                "        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->n" +
                "        <div class="pswp__ui pswp__ui--hidden">n" +
                "n" +
                "            <div class="pswp__top-bar">n" +
                "n" +
                "                <!--  Controls are self-explanatory. Order can be changed. -->n" +
                "n" +
                "                <div class="pswp__counter"></div>n" +
                "n" +
                "                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>n" +
                "n" +
                "                <button class="pswp__button pswp__button--share" title="Share"></button>n" +
                "n" +
                "                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>n" +
                "n" +
                "                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>n" +
                "n" +
                "                <!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR -->n" +
                "                <!-- element will get class pswp__preloader--active when preloader is running -->n" +
                "                <div class="pswp__preloader">n" +
                "                    <div class="pswp__preloader__icn">n" +
                "                      <div class="pswp__preloader__cut">n" +
                "                        <div class="pswp__preloader__donut"></div>n" +
                "                      </div>n" +
                "                    </div>n" +
                "                </div>n" +
                "            </div>n" +
                "n" +
                "            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">n" +
                "                <div class="pswp__share-tooltip"></div> n" +
                "            </div>n" +
                "n" +
                "            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">n" +
                "            </button>n" +
                "n" +
                "            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">n" +
                "            </button>n" +
                "n" +
                "            <div class="pswp__caption">n" +
                "                <div class="pswp__caption__center"></div>n" +
                "            </div>n" +
                "n" +
                "        </div>n" +
                "n" +
                "    </div>n" +
                "n" +
                "</div>";
    }

В документации приводится пример включения плагина по клику на изображение, но отмечается, что PhotoSwipe все равно как именно он будет запускаться. В итоге, я написал свой скрипт. Я показываю статью с чужого сайта в приложении, так что получаю уже готовый html. Чтобы динамически не работать с версткой, я в своем скрипте всем изображениям (потому что лишних в статье точно нет) меняю класс, присваиваю обработчик событий (при клике на который передается url — он служит идентификатором для галереи, и запускается плагин).

Плагину нужно знать размеры изображения. Поскольку галерея подключается когда вся страница загружена, узнать конечные размеры изображений просто, через свойства naturalWidth и naturalHeight изображения.

Код скрипта. Сначала при загрузке страницы запускается функция init(); Потом при клике на изображение openGallery().

Код

// Меняем всем изображением класс и вешаем обработчик событий
function init() {
	var images = document.images;
	for (var i = 0; i < images.length; i++) {
		images[i].className = "photoSwipe";
		images[i].onclick = function () { openGalery(this.attributes["src"].value); }
	}
}
// Собираем все необходимое
function openGalery(url) {
	var images =  document.getElementsByClassName("photoSwipe");
	var items = new Array();
	for (var i = 0; i < images.length; i++) {
        items.push(getAttributes(images[i]));
    }
    startPhotoSwipe(items, getIndex(images, url));
}
// Получаем аттрибуты изображения
function getAttributes(image) {
    return {
        src: image.attributes["src"].value,
        w: image.naturalWidth,
        h: image.naturalHeight
    }
}
// Получаем номер изображения, на которое нажали
function getIndex(images, url) {
	for(var i = 0; i < images.length; i++) {
		if (url == images[i].attributes["src"].value)
		    return i;
	}
	return false;
}
// Запускаем плагин
function startPhotoSwipe(items, index) {
	var pswpElement = document.querySelectorAll('.pswp')[0];
    // define options (if needed)
    var options = {
        // optionName: 'option value'
        // for example:
        index: index, // start at index slide
        bgOpacity: 0.9,
        pinchToClose: false,
        fullscreenEl: false,
        closeEl:false,
        zoomEl: false,
        shareEl: false,
        indexIndicatorSep: ' из ',
        tapToClose: true
    };
    callAndroid.setGallery(true); // Эта строчка нужно для Java кода андроид, об этом ниже
    // Initializes and opens PhotoSwipe
    var gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options);
    gallery.init();
}

Дальше необходимо настроить WebView и загрузить страницу.

webView = findViewById(R.id.webView);
            webView.setWebViewClient(new WebViewClient());
            webView.setWebChromeClient(new WebChromeClient());
            webView.getSettings().setJavaScriptEnabled(true);
            webView.getSettings().setDomStorageEnabled(true);

На этом этапе страница загружается, плагин стартует при нажатии на изображение, но при нажатии кнопки назад при открытой галерее закрывается все приложение, а не плагин.

Чтобы это исправить я сделал мост Javascipt.

Для начала нужно сделать singleton с состоянием галереи.

Код

package me.denko.photoswipe;
class GalleryState {
    private static final GalleryState ourInstance = new GalleryState();
    static GalleryState getInstance() {
        return ourInstance;
    }
    private GalleryState() {
    }
    private boolean isGallery = false;
    public boolean isGallery() {
        return isGallery;
    }
    public void setGallery(boolean gallery) {
        isGallery = gallery;
    }
}

Дальше интерфейс для вызова из WebView

Код

package me.denko.photoswipe;
import android.webkit.JavascriptInterface;
public class JavascriptFromWebView {
    @JavascriptInterface
    public static void setGallery(boolean bool) {
        GalleryState.getInstance().setGallery(bool);
    }
}

Нужно подключить этот интерфейс к WebView

webView.addJavascriptInterface(new JavascriptFromWebView(), "callAndroid");

В скрипт открытия галереи нужно дописать

callAndroid.setGallery(true);

В скрипт закрытия галереи в файле photoswipe.js

Код

close: function() {
		if(!_isOpen) {
			return;
		}
		callAndroid.setGallery(false); // Дописывается эта строка
		_isOpen = false;
		_isDestroying = true;
		_shout('close');
		_unbindEvents();
		_showOrHide(self.currItem, null, true, self.destroy);
	},

Теперь при открытии галереи состоянию присваивается true, при закрытии false. Осталось только добавить обрабочик нажатия кнопки назад WebView.

Код

webView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() != KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
                    if (GalleryState.getInstance().isGallery()) {
                        webView.reload();
                        GalleryState.getInstance().setGallery(false);
                    } else
                        onBackPressed();
                }
                return true;
            }
        });

Теперь галерею можно открыть и потом закрыть кнопкой назад.

Полный код приложения на github.

Критика кода приветствуется. Если я что-то сделал не так, прошу написать об этом в комментариях.

Теги:

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.