Альтернативой является предоставление вашим текущим пользователям уникального кода, например, David123 или HenryABC. Они делятся своим кодом (который легко запомнить, поскольку он личный). В вашем приложении вы предоставляете реферальный блок, в который новый пользователь вводит код, чтобы разблокировать дополнительные функции. Таким образом, вы можете отслеживать и не беспокоиться о отпечатке пальца. Большая проблема с методом отпечатка пальца - iOS недостаточно уникальна, а мобильные сети используют IP-адреса. Так что кто-то с iPhone 6s в Лондоне на EE, вероятно, будет иметь тот же отпечаток пальца, что и 100 других людей.
Вопрос в том, как манипулировать изображением, подобным 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) } }