Вопрос в том, как манипулировать изображением, подобным ImageView.ScaleType.CENTER_CROP
, но сместить фокус из центра в другое место, которое находится на 20% от верхней части изображения. Сначала давайте посмотрим, что делает CENTER_CROP
:
CENTER_CROP
Scale изображение равномерно (сохраняйте пропорции изображения), так что оба размера (ширина и высота) изображения будут равны или больше соответствующего размера вида (минус отступ). Затем изображение центрируется на виде. Из XML используйте этот синтаксис:
blockquote>android:scaleType="centerCrop"
.Другими словами, масштабируйте изображение без искажений так, чтобы ширина или высота изображения (или ширина, и высота) вписывались в вид, чтобы вид был полностью заполнен изображением (без пропусков). .)
Еще один способ думать об этом состоит в том, что центр изображения «прикреплен» к центру изображения. Затем изображение масштабируется в соответствии с указанными выше критериями.
В следующем видео белые линии отмечают центр изображения; красные линии отмечают центр зрения. Тип шкалы -
CENTER_CROP
. Обратите внимание, как центральные точки изображения и вида совпадают. При изменении размера представления эти две точки продолжают перекрываться и всегда отображаются в центре вида независимо от размера представления.Итак, что значит иметь подобный центру поведение в другом месте, например, на 20% сверху? Как и в центре обрезки, мы можем указать, что точка, которая находится в 20% от верхней части изображения, и точка, в которой 20% от верхней части вида будут «закреплены», как точка 50% «закреплена» в центре обрезки. Горизонтальное расположение этой точки остается на уровне 50% изображения и вида. Теперь изображение можно масштабировать для соответствия другим условиям обрезки по центру, которые указывают, что ширина и / или высота изображения будут соответствовать виду без зазоров. (Под размером представления понимается размер представления без заполнения).
Вот короткое видео об этом поведении кадрирования на 20%. В этом видео белые линии показывают середину изображения, красные линии показывают закрепленную точку на виде, а синяя линия, которая отображается за горизонтальной красной линией, обозначает 20% от верхней части изображения. (Демонстрационный проект находится на GitHub .
Вот результат, показывающий полное изображение, которое было предоставлено, и видео в квадратной рамке, переходящее из неподвижного изображения.
MainActivity.kt
[ 1115]prepareMatrix()
- это метод, позволяющий определить, как масштабировать / обрезать изображение. Необходимо выполнить дополнительную работу с видео, так как кажется, что видео сделано в соответствии сTextureView
как масштаб введите "FIT_XY", когда он назначен дляTextureView
. Из-за этого масштабирования размер носителя должен быть восстановлен до того, какprepareMatrix()
будет вызвано для видеоclass MainActivity : AppCompatActivity() { private val imageResId = R.drawable.test private val videoResId = R.raw.test private var player: SimpleExoPlayer? = null private val mFocalPoint = PointF(0.5f, 0.2f) override fun onCreate(savedInstanceState: Bundle?) { window.setBackgroundDrawable(ColorDrawable(0xff000000.toInt())) super.onCreate(savedInstanceState) if (cache == null) { cache = SimpleCache(File(cacheDir, "media"), LeastRecentlyUsedCacheEvictor(MAX_PREVIEW_CACHE_SIZE_IN_BYTES)) } setContentView(R.layout.activity_main) // imageView.visibility = View.INVISIBLE imageView.setImageResource(imageResId) imageView.doOnPreDraw { imageView.scaleType = ImageView.ScaleType.MATRIX val imageWidth: Float = ContextCompat.getDrawable(this, imageResId)!!.intrinsicWidth.toFloat() val imageHeight: Float = ContextCompat.getDrawable(this, imageResId)!!.intrinsicHeight.toFloat() imageView.imageMatrix = prepareMatrix(imageView, imageWidth, imageHeight, mFocalPoint, Matrix()) val b = BitmapFactory.decodeResource(resources, imageResId) val d = BitmapDrawable(resources, b.copy(Bitmap.Config.ARGB_8888, true)) val c = Canvas(d.bitmap) val p = Paint() p.color = resources.getColor(android.R.color.holo_red_dark) p.style = Paint.Style.STROKE val strokeWidth = 10 p.strokeWidth = strokeWidth.toFloat() // Horizontal line c.drawLine(0f, imageHeight * mFocalPoint.y, imageWidth, imageHeight * mFocalPoint.y, p) // Vertical line c.drawLine(imageWidth * mFocalPoint.x, 0f, imageWidth * mFocalPoint.x, imageHeight, p) // Line in horizontal and vertical center p.color = resources.getColor(android.R.color.white) c.drawLine(imageWidth / 2, 0f, imageWidth / 2, imageHeight, p) c.drawLine(0f, imageHeight / 2, imageWidth, imageHeight / 2, p) imageView.setImageBitmap(d.bitmap) imageViewFull.setImageBitmap(d.bitmap) } } fun startPlay(view: View) { playVideo() } private fun getViewWidth(view: View): Float { return (view.width - view.paddingStart - view.paddingEnd).toFloat() } private fun getViewHeight(view: View): Float { return (view.height - view.paddingTop - view.paddingBottom).toFloat() } private fun prepareMatrix(targetView: View, mediaWidth: Float, mediaHeight: Float, focalPoint: PointF, matrix: Matrix): Matrix { if (targetView.visibility != View.VISIBLE) { return matrix } val viewHeight = getViewHeight(targetView) val viewWidth = getViewWidth(targetView) val scaleFactorY = viewHeight / mediaHeight val scaleFactor: Float val px: Float val py: Float if (mediaWidth * scaleFactorY >= viewWidth) { // Fit height scaleFactor = scaleFactorY px = -(mediaWidth * scaleFactor - viewWidth) * focalPoint.x / (1 - scaleFactor) py = 0f } else { // Fit width scaleFactor = viewWidth / mediaWidth px = 0f py = -(mediaHeight * scaleFactor - viewHeight) * focalPoint.y / (1 - scaleFactor) } matrix.postScale(scaleFactor, scaleFactor, px, py) return matrix } private fun playVideo() { player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector()) player!!.setVideoTextureView(textureView) player!!.addVideoListener(object : VideoListener { override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) { super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio) val matrix = Matrix() // Restore true media size for further manipulation. matrix.setScale(width / getViewWidth(textureView), height / getViewHeight(textureView)) textureView.setTransform(prepareMatrix(textureView, width.toFloat(), height.toFloat(), mFocalPoint, matrix)) } override fun onRenderedFirstFrame() { Log.d("AppLog", "onRenderedFirstFrame") player!!.removeVideoListener(this) imageView.animate().alpha(0f).setDuration(2000).start() imageView.visibility = View.INVISIBLE } }) player!!.volume = 0f player!!.repeatMode = Player.REPEAT_MODE_ALL player!!.playRawVideo(this, videoResId) player!!.playWhenReady = true // player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/240/big_buck_bunny_240p_20mb.mkv", cache!!) // player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv", cache!!) // player!!.playVideoFromUrl(this@MainActivity, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv") } override fun onStop() { super.onStop() if (player != null) { player!!.setVideoTextureView(null) // playerView.player = null player!!.release() player = null } } companion object { const val MAX_PREVIEW_CACHE_SIZE_IN_BYTES = 20L * 1024L * 1024L var cache: com.google.android.exoplayer2.upstream.cache.Cache? = null @JvmStatic fun getUserAgent(context: Context): String { val packageManager = context.packageManager val info = packageManager.getPackageInfo(context.packageName, 0) val appName = info.applicationInfo.loadLabel(packageManager).toString() return Util.getUserAgent(context, appName) } } fun SimpleExoPlayer.playRawVideo(context: Context, @RawRes rawVideoRes: Int) { val dataSpec = DataSpec(RawResourceDataSource.buildRawResourceUri(rawVideoRes)) val rawResourceDataSource = RawResourceDataSource(context) rawResourceDataSource.open(dataSpec) val factory: DataSource.Factory = DataSource.Factory { rawResourceDataSource } prepare(LoopingMediaSource(ExtractorMediaSource.Factory(factory).createMediaSource(rawResourceDataSource.uri))) } fun SimpleExoPlayer.playVideoFromUrl(context: Context, url: String, cache: Cache? = null) = playVideoFromUri(context, Uri.parse(url), cache) fun SimpleExoPlayer.playVideoFile(context: Context, file: File) = playVideoFromUri(context, Uri.fromFile(file)) fun SimpleExoPlayer.playVideoFromUri(context: Context, uri: Uri, cache: Cache? = null) { val factory = if (cache != null) CacheDataSourceFactory(cache, DefaultHttpDataSourceFactory(getUserAgent(context))) else DefaultDataSourceFactory(context, MainActivity.getUserAgent(context)) val mediaSource = ExtractorMediaSource.Factory(factory).createMediaSource(uri) prepare(mediaSource) } }
Как предположил скаффман , Файлы тегов JSP 2.0 - это колени пчелы.
Возьмем ваш простой пример.
Поместите следующее в WEB-INF / tags / wrapper.tag
<%@tag description="Simple Wrapper Tag" pageEncoding="UTF-8"%>
<html><body>
<jsp:doBody/>
</body></html>
Теперь на вашей странице example.jsp
:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<t:wrapper>
<h1>Welcome</h1>
</t:wrapper>
Это именно то, что вы думаете.
Итак, давайте расширим это до чего-то более общего.
WEB-INF / tags / genericpage.tag
<%@tag description="Overall Page template" pageEncoding="UTF-8"%>
<%@attribute name="header" fragment="true" %>
<%@attribute name="footer" fragment="true" %>
<html>
<body>
<div id="pageheader">
<jsp:invoke fragment="header"/>
</div>
<div id="body">
<jsp:doBody/>
</div>
<div id="pagefooter">
<jsp:invoke fragment="footer"/>
</div>
</body>
</html>
Чтобы использовать это:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<t:genericpage>
<jsp:attribute name="header">
<h1>Welcome</h1>
</jsp:attribute>
<jsp:attribute name="footer">
<p id="copyright">Copyright 1927, Future Bits When There Be Bits Inc.</p>
</jsp:attribute>
<jsp:body>
<p>Hi I'm the heart of the message</p>
</jsp:body>
</t:genericpage>
Что это дает вам? На самом деле, много, но становится еще лучше ...
WEB-INF / tags / userpage.tag
<%@tag description="User Page template" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@attribute name="userName" required="true"%>
<t:genericpage>
<jsp:attribute name="header">
<h1>Welcome ${userName}</h1>
</jsp:attribute>
<jsp:attribute name="footer">
<p id="copyright">Copyright 1927, Future Bits When There Be Bits Inc.</p>
</jsp:attribute>
<jsp:body>
<jsp:doBody/>
</jsp:body>
</t:genericpage>
Чтобы использовать это: (предположим, что у нас есть пользовательская переменная в запросе)
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<t:userpage userName="${user.fullName}">
<p>
First Name: ${user.firstName} <br/>
Last Name: ${user.lastName} <br/>
Phone: ${user.phone}<br/>
</p>
</t:userpage>
Но оказывается, вам нравится использовать этот блок сведений о пользователе в других местах. Итак, мы проведем рефакторинг.
WEB-INF / теги / userdetail.tag
<%@tag description="User Page template" pageEncoding="UTF-8"%>
<%@tag import="com.example.User" %>
<%@attribute name="user" required="true" type="com.example.User"%>
First Name: ${user.firstName} <br/>
Last Name: ${user.lastName} <br/>
Phone: ${user.phone}<br/>
Теперь предыдущий пример выглядит следующим образом:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<t:userpage userName="${user.fullName}">
<p>
<t:userdetail user="${user}"/>
</p>
</t:userpage>
Прелесть файлов тегов JSP заключается в том, что они позволяют вам помечать общую разметку, а затем рефакторировать ее так, как вам нравится.
Файлы тегов JSP
в значительной степени узурпировали такие вещи, как Tiles
и т. Д., По крайней мере, для меня. Я считаю, что их намного проще использовать, так как единственная структура - это то, что вы ей даете, без предвзятости. Кроме того, вы можете использовать файлы тегов JSP для других целей (например, фрагмент сведений о пользователе выше).
Вот пример, похожий на DisplayTag, который я сделал, но все это делается с помощью файлов тегов (и фреймворка Stripes
, то есть s: tags ..). В результате получается таблица строк, чередующихся цветов, навигации по страницам и т. Д .:
<t:table items="${actionBean.customerList}" var="obj" css_class="display">
<t:col css_class="checkboxcol">
<s:checkbox name="customerIds" value="${obj.customerId}"
onclick="handleCheckboxRangeSelection(this, event);"/>
</t:col>
<t:col name="customerId" title="ID"/>
<t:col name="firstName" title="First Name"/>
<t:col name="lastName" title="Last Name"/>
<t:col>
<s:link href="/Customer.action" event="preEdit">
Edit
<s:param name="customer.customerId" value="${obj.customerId}"/>
<s:param name="page" value="${actionBean.page}"/>
</s:link>
</t:col>
</t:table>
Конечно, теги работают с тегами JSTL
(например, c: if
и т. Д.). Единственное, что вы не можете сделать в теле тега файла тега, - это добавить код скриптлета Java, но это не такое большое ограничение, как вы могли подумать. Если мне нужны скриптлеты, я просто вставляю логику в тег и вставляю его. Легко.
Итак, файлы тегов могут быть практически такими, какими вы хотите их видеть. На самом базовом уровне это простой рефакторинг вырезания и вставки. Возьмите кусок макета, вырежьте его, выполните простую параметризацию и замените его вызовом тега.
На более высоком уровне вы можете делать сложные вещи вроде этого тега таблицы, который у меня здесь.
добавьте dependecys для использования < % @tag описание = "Шаблон User Page" pageEncoding = % "UTF-8">
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>javax.servlet.jsp.jstl-api</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
Это может также быть достигнуто с jsp:include. Чадское Правило штукатура объясняет хорошо здесь в этом видео https://www.youtube.com/watch? v=EWbYj0qoNHo
Используйте плитки . Это спасло мне жизнь.
Но если вы не можете, есть тег include , что делает его похожим на php.
Тег body может не делать то, что вам нужно, если только у вас нет сверхпростого содержимого. Тег body используется для определения тела указанного элемента. Взгляните на этот пример :
<jsp:element name="${content.headerName}"
xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:attribute name="lang">${content.lang}</jsp:attribute>
<jsp:body>${content.body}</jsp:body>
</jsp:element>
Вы указываете имя элемента, любые атрибуты, которые может иметь элемент (в данном случае «lang»), а затем текст, который идет в нем - тело. Итак, если
content.headerName = h1
, content.lang = fr
и content.body = Заголовок на французском языке
, то вывод будет
<h1 lang="fr">Heading in French</h1>