Я не профессиональный разработчик, хотя и учился на программиста. Сейчас работаю системным администратором и планирую переходить в разработчики. Пишу для себя приложение, которое парсит один из популярных сайтов тематики IT и показывает статьи в нативном приложении android. Захотелось, чтобы все картинки в статье открывались в галерее, как в мобильном приложении habr.
Скорее всего, руководство получится простым, но мне хотется получить feedback и, скорее всего, кому-то это руководство окажется полезным.
Я попробовал несколько плагинов. Несколько не запустились, другие требуют JQuery, а это замедляет загрузку страницы в приложении. В итоге, я выбрал плагин PhotoSwipe. плагин не требует JQuery, работает быстро и почти ничего не весит.
Для начала нужно подумать где в приложении лучше разместить исходные файлы плагина. Сначала по неопытности я размещал файлы в папку raw, но лучше размещать их в папку assets. Правой кнопкой мыши по app>new > folder>Assets Folder
Разметка будет одно простое WebView:
Дальше нужно скачать или клонировать репозиторий с PhotoSwipe (ссылка на сайте плагина) и скопировать все содержимое папки dist в assets (dist можно переименовать в PhotoSwipe. Еще можно создать файл style.css в папке css, чтобы изображения занимали всю ширину.
Для галереи нужно подключить стили, javascipt файлы и файл стилей для скина. Можно сформировать html:
Чтобы плагин заработал, так же нужно вставить контейнер для галереи в исходники страницы. В документации написано, что это не простой плагин и нужно часть работы сделать самостоятельно.
Код желательно вставлять перед закрывающим тегом
В документации приводится пример включения плагина по клику на изображение, но отмечается, что PhotoSwipe все равно как именно он будет запускаться. В итоге, я написал свой скрипт. Я показываю статью с чужого сайта в приложении, так что получаю уже готовый html. Чтобы динамически не работать с версткой, я в своем скрипте всем изображениям (потому что лишних в статье точно нет) меняю класс, присваиваю обработчик событий (при клике на который передается url — он служит идентификатором для галереи, и запускается плагин).
Плагину нужно знать размеры изображения. Поскольку галерея подключается когда вся страница загружена, узнать конечные размеры изображений просто, через свойства naturalWidth и naturalHeight изображения.
Код скрипта. Сначала при загрузке страницы запускается функция init(); Потом при клике на изображение openGallery().
Дальше необходимо настроить WebView и загрузить страницу.
На этом этапе страница загружается, плагин стартует при нажатии на изображение, но при нажатии кнопки назад при открытой галерее закрывается все приложение, а не плагин.
Чтобы это исправить я сделал мост Javascipt.
Для начала нужно сделать singleton с состоянием галереи.
Дальше интерфейс для вызова из WebView
Нужно подключить этот интерфейс к WebView
В скрипт открытия галереи нужно дописать
В скрипт закрытия галереи в файле photoswipe.js
Теперь при открытии галереи состоянию присваивается true, при закрытии false. Осталось только добавить обрабочик нажатия кнопки назад WebView.
Теперь галерею можно открыть и потом закрыть кнопкой назад.
Полный код приложения на github.
Критика кода приветствуется. Если я что-то сделал не так, прошу написать об этом в комментариях.
Скорее всего, руководство получится простым, но мне хотется получить 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.
Критика кода приветствуется. Если я что-то сделал не так, прошу написать об этом в комментариях.
STFBEE
Простите, но что тут нативного?
Кроме вызова метода js из java кода, что делается одним копипастом с so.
Кстати, слушатель события клавиш, емнип, не вызовется, если фокус будет на каком-то другом элементе, например на Options Menu (но это не точно, нужно проверить). Хорошей практикой все же будет переопределение onBackPressed() и встраивание логики обработки «назад» туда.
also_east Автор
Про нативность я писал, когда говорил о своем приложении. Оно парсит сайт, показывает статьи в приложении android так, как настроено. Это, мне кажется, нативность. У меня все сложнее, я специально оставил только то, что нужно, чтобы подключить плагин.
У меня оно показывается через ViewPager и фрагменты, без меню, так что фокус всегда на WebView. Единственное, нужно было еще создать свой класс наследник от ViewPager и переопределить
Иначе если скроллить с картинки, оно работало, но если начинать скроллить от края экрана, открывался соседний фрагмент