라이브 배경 화면, 가운데 자르기 및 너비 / 높이에 맞추는 방법은 무엇입니까?
배경
영상을 볼 수있는 라이브 배경 화면을 만들고 있습니다. 처음에는 이것이 매우 어려울 것이라고 생각했기 때문에 일부 사람들은 OpenGL 솔루션 또는 기타 매우 복잡한 솔루션 (예 : 이 솔루션)을 사용하도록 제안했습니다 .
어쨌든, 이것에 대해 여러 곳에서 이야기하는 것을 발견했고,이 github 라이브러리 (버그가 있음)를 기반으로 마침내 작동하게되었습니다.
문제
동영상 상영에 성공했지만 화면 해상도와 비교하여 표시 방법을 제어 할 수 없습니다.
현재는 항상이 (비디오에서 가져온 것을 의미 화면 크기로 뻗어 될 얻는다 여기 )
다음과 같이 표시됩니다.
그 이유는 가로 세로 비율이 다르기 때문입니다 : 560x320 (동영상 해상도) 대 1080x1920 (기기 해상도).
참고 : 다양한 Github 리포지토리 (예 : 여기 )에서 사용할 수있는 비디오 크기 조정 솔루션에 대해 잘 알고 있지만 라이브 배경 화면에 대해 묻고 있습니다. 따라서 뷰가 없으므로 작업 방법에 대해 더 제한적입니다. 더 구체적으로 말하면 솔루션에는 어떤 종류의 레이아웃, TextureView 또는 SurfaceView 또는 다른 종류의 View도있을 수 없습니다.
내가 시도한 것
SurfaceHolder의 다양한 분야와 기능을 가지고 놀려고했지만 지금까지는 운이 없었습니다. 예 :
setVideoScalingMode- 충돌하거나 아무것도하지 않습니다.
surfaceFrame 변경 -동일합니다.
여기에 내가 만들어 놓은 현재 코드 (사용 가능한 전체 프로젝트의 여기가 ) :
class MovieLiveWallpaperService : WallpaperService() {
override fun onCreateEngine(): WallpaperService.Engine {
return VideoLiveWallpaperEngine()
}
private enum class PlayerState {
NONE, PREPARING, READY, PLAYING
}
inner class VideoLiveWallpaperEngine : WallpaperService.Engine() {
private var mp: MediaPlayer? = null
private var playerState: PlayerState = PlayerState.NONE
override fun onSurfaceCreated(holder: SurfaceHolder) {
super.onSurfaceCreated(holder)
Log.d("AppLog", "onSurfaceCreated")
mp = MediaPlayer()
val mySurfaceHolder = MySurfaceHolder(holder)
mp!!.setDisplay(mySurfaceHolder)
mp!!.isLooping = true
mp!!.setVolume(0.0f, 0.0f)
mp!!.setOnPreparedListener { mp ->
playerState = PlayerState.READY
setPlay(true)
}
try {
//mp!!.setDataSource(this@MovieLiveWallpaperService, Uri.parse("http://techslides.com/demos/sample-videos/small.mp4"))
mp!!.setDataSource(this@MovieLiveWallpaperService, Uri.parse("android.resource://" + packageName + "/" + R.raw.small))
} catch (e: Exception) {
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("AppLog", "onDestroy")
if (mp == null)
return
mp!!.stop()
mp!!.release()
playerState = PlayerState.NONE
}
private fun setPlay(play: Boolean) {
if (mp == null)
return
if (play == mp!!.isPlaying)
return
when {
!play -> {
mp!!.pause()
playerState = PlayerState.READY
}
mp!!.isPlaying -> return
playerState == PlayerState.READY -> {
Log.d("AppLog", "ready, so starting to play")
mp!!.start()
playerState = PlayerState.PLAYING
}
playerState == PlayerState.NONE -> {
Log.d("AppLog", "not ready, so preparing")
mp!!.prepareAsync()
playerState = PlayerState.PREPARING
}
}
}
override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
Log.d("AppLog", "onVisibilityChanged:" + visible + " " + playerState)
if (mp == null)
return
setPlay(visible)
}
}
class MySurfaceHolder(private val surfaceHolder: SurfaceHolder) : SurfaceHolder {
override fun addCallback(callback: SurfaceHolder.Callback) = surfaceHolder.addCallback(callback)
override fun getSurface() = surfaceHolder.surface!!
override fun getSurfaceFrame() = surfaceHolder.surfaceFrame
override fun isCreating(): Boolean = surfaceHolder.isCreating
override fun lockCanvas(): Canvas = surfaceHolder.lockCanvas()
override fun lockCanvas(dirty: Rect): Canvas = surfaceHolder.lockCanvas(dirty)
override fun removeCallback(callback: SurfaceHolder.Callback) = surfaceHolder.removeCallback(callback)
override fun setFixedSize(width: Int, height: Int) = surfaceHolder.setFixedSize(width, height)
override fun setFormat(format: Int) = surfaceHolder.setFormat(format)
override fun setKeepScreenOn(screenOn: Boolean) {}
override fun setSizeFromLayout() = surfaceHolder.setSizeFromLayout()
override fun setType(type: Int) = surfaceHolder.setType(type)
override fun unlockCanvasAndPost(canvas: Canvas) = surfaceHolder.unlockCanvasAndPost(canvas)
}
}
질문
가로 세로 비율을 유지하면서 ImageView에 대한 내용을 기반으로 콘텐츠 크기를 조정하는 방법을 알고 싶습니다.
- 중앙 자르기-컨테이너 (이 경우 화면)의 100 %에 맞고 필요한 경우 측면 (위쪽 및 아래쪽 또는 왼쪽 및 오른쪽)에서 자릅니다. 아무것도 늘이지 않습니다. 이는 콘텐츠가 괜찮아 보이지만 일부만 표시 될 수 있음을 의미합니다.
- 맞춤 중심-너비 / 높이에 맞게 늘이기
- center-inside-원래 크기로 설정하고, 중앙에 맞추고, 너무 큰 경우에만 너비 / 높이에 맞게 늘립니다.
그래서 저는 아직 당신이 요청한 모든 스케일 유형을 얻을 수 없었지만 엑소 플레이어를 사용하여 상당히 쉽게 fit-xy 및 center-crop 작업을 수행 할 수있었습니다. 전체 코드는 https://github.com/yperess/StackOverflow/tree/50091878 에서 볼 수 있으며 더 많은 정보를 얻을 때마다 업데이트하겠습니다. 결국에는 설정으로 확장 유형을 선택하고 (간단한 PreferenceActivity로이 작업을 수행 할 것임) 서비스 측에서 공유 된 기본 설정 값을 읽을 수 있도록 MainActivity도 채울 것입니다.
전반적인 아이디어는 MediaCodec에서 이미 fit-xy 및 center-crop을 모두 구현하고 있으며 이는 뷰 계층 구조에 액세스 할 수있는 경우에 필요한 유일한 두 가지 모드입니다. 왜냐하면 fit-center, fit-top, fit-bottom은 표면에 중력이 있고 비디오 크기 * 최소 크기 조정과 일치하도록 크기가 조정되는 경우 모두 실제로 fit-xy이기 때문입니다. 이러한 작업을 수행하려면 OpenGL 컨텍스트를 만들고 SurfaceTexture를 제공해야한다고 생각합니다. 이 SurfaceTexture는 exo 플레이어에게 전달할 수있는 스텁 Surface로 래핑 할 수 있습니다. 비디오가로드되면 생성 한 이후 크기를 설정할 수 있습니다. 또한 SurfaceTexture에는 프레임이 준비되면 알려주는 콜백이 있습니다. 이 시점에서 프레임을 수정할 수 있어야합니다 (간단한 매트릭스 스케일 및 변환을 사용하기를 바랍니다).
여기서 핵심 구성 요소는 엑소 플레이어를 만드는 것입니다.
private fun initExoMediaPlayer(): SimpleExoPlayer {
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
val player = ExoPlayerFactory.newSimpleInstance(this@MovieLiveWallpaperService,
trackSelector)
player.playWhenReady = true
player.repeatMode = Player.REPEAT_MODE_ONE
player.volume = 0f
if (mode == Mode.CENTER_CROP) {
player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
} else {
player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
}
if (mode == Mode.FIT_CENTER) {
player.addVideoListener(this)
}
return player
}
그런 다음 비디오를로드합니다.
override fun onSurfaceCreated(holder: SurfaceHolder) {
super.onSurfaceCreated(holder)
if (mode == Mode.FIT_CENTER) {
// We need to somehow wrap the surface or set some scale factor on exo player here.
// Most likely this will require creating a SurfaceTexture and attaching it to an
// OpenGL context. Then for each frame, writing it to the original surface but with
// an offset
exoMediaPlayer.setVideoSurface(holder.surface)
} else {
exoMediaPlayer.setVideoSurfaceHolder(holder)
}
val videoUri = RawResourceDataSource.buildRawResourceUri(R.raw.small)
val dataSourceFactory = DataSource.Factory { RawResourceDataSource(context) }
val mediaSourceFactory = ExtractorMediaSource.Factory(dataSourceFactory)
exoMediaPlayer.prepare(mediaSourceFactory.createMediaSource(videoUri))
}
최신 정보:
잘 작동합니다. 코드를 게시하기 전에 내일 정리해야합니다. 여기에 미리보기가 있습니다.
기본적으로 GLSurfaceView를 가져 와서 쪼개는 결과를 얻었습니다. 소스를 살펴보면 벽지에서 사용할 수 없게 만드는 유일한 누락은 창에 부착 될 때만 GLThread를 시작한다는 사실입니다. 따라서 동일한 코드를 복제하지만 GLThread를 수동으로 시작하도록 허용하면 계속 진행할 수 있습니다. 그 후에는 그리는 쿼드를 맞추고 이동하는 최소 스케일로 스케일링 한 후 화면이 비디오에 비해 얼마나 큰지 추적하면됩니다.
코드의 알려진 문제 : 1. GLThread에 작은 버그가 있습니다. 스레드가 일시 중지 될 때 signallAll()
실제로 아무것도 기다리지 않는 호출을받는 간단한 타이밍 문제가있는 것 같습니다 . 2. 렌더러에서 모드를 동적으로 수정하지 않았습니다. 너무 힘들어서는 안됩니다. 엔진을 만들 때 기본 설정 리스너를 추가 한 다음 scale_type
변경 시 렌더러를 업데이트합니다 .
업데이트 : 모든 문제가 해결되었습니다. signallAll()
우리가 실제로 자물쇠를 가지고 있는지 확인하기 위해 수표를 놓 쳤기 때문에 던졌습니다. 또한 스케일 유형을 동적으로 업데이트하는 리스너를 추가하여 이제 모든 스케일 유형이 GlEngine을 사용합니다.
즐겨!
TextureView로이를 달성 할 수 있습니다. (surfaceView도 작동하지 않습니다) 이것을 달성하는 데 도움이 될 몇 가지 코드를 찾았습니다.
이 데모에서는 중앙, 상단 및 하단의 세 가지 유형으로 비디오를자를 수 있습니다 .
TextureVideoView.java
public class TextureVideoView extends TextureView implements TextureView.SurfaceTextureListener {
// Indicate if logging is on
public static final boolean LOG_ON = true;
// Log tag
private static final String TAG = TextureVideoView.class.getName();
private MediaPlayer mMediaPlayer;
private float mVideoHeight;
private float mVideoWidth;
private boolean mIsDataSourceSet;
private boolean mIsViewAvailable;
private boolean mIsVideoPrepared;
private boolean mIsPlayCalled;
private ScaleType mScaleType;
private State mState;
public enum ScaleType {
CENTER_CROP, TOP, BOTTOM
}
public enum State {
UNINITIALIZED, PLAY, STOP, PAUSE, END
}
public TextureVideoView(Context context) {
super(context);
initView();
}
public TextureVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public TextureVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
initPlayer();
setScaleType(ScaleType.CENTER_CROP);
setSurfaceTextureListener(this);
}
public void setScaleType(ScaleType scaleType) {
mScaleType = scaleType;
}
private void updateTextureViewSize() {
float viewWidth = getWidth();
float viewHeight = getHeight();
float scaleX = 1.0f;
float scaleY = 1.0f;
if (mVideoWidth > viewWidth && mVideoHeight > viewHeight) {
scaleX = mVideoWidth / viewWidth;
scaleY = mVideoHeight / viewHeight;
} else if (mVideoWidth < viewWidth && mVideoHeight < viewHeight) {
scaleY = viewWidth / mVideoWidth;
scaleX = viewHeight / mVideoHeight;
} else if (viewWidth > mVideoWidth) {
scaleY = (viewWidth / mVideoWidth) / (viewHeight / mVideoHeight);
} else if (viewHeight > mVideoHeight) {
scaleX = (viewHeight / mVideoHeight) / (viewWidth / mVideoWidth);
}
// Calculate pivot points, in our case crop from center
int pivotPointX;
int pivotPointY;
switch (mScaleType) {
case TOP:
pivotPointX = 0;
pivotPointY = 0;
break;
case BOTTOM:
pivotPointX = (int) (viewWidth);
pivotPointY = (int) (viewHeight);
break;
case CENTER_CROP:
pivotPointX = (int) (viewWidth / 2);
pivotPointY = (int) (viewHeight / 2);
break;
default:
pivotPointX = (int) (viewWidth / 2);
pivotPointY = (int) (viewHeight / 2);
break;
}
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY, pivotPointX, pivotPointY);
setTransform(matrix);
}
private void initPlayer() {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
} else {
mMediaPlayer.reset();
}
mIsVideoPrepared = false;
mIsPlayCalled = false;
mState = State.UNINITIALIZED;
}
/**
* @see MediaPlayer#setDataSource(String)
*/
public void setDataSource(String path) {
initPlayer();
try {
mMediaPlayer.setDataSource(path);
mIsDataSourceSet = true;
prepare();
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
/**
* @see MediaPlayer#setDataSource(Context, Uri)
*/
public void setDataSource(Context context, Uri uri) {
initPlayer();
try {
mMediaPlayer.setDataSource(context, uri);
mIsDataSourceSet = true;
prepare();
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
/**
* @see MediaPlayer#setDataSource(java.io.FileDescriptor)
*/
public void setDataSource(AssetFileDescriptor afd) {
initPlayer();
try {
long startOffset = afd.getStartOffset();
long length = afd.getLength();
mMediaPlayer.setDataSource(afd.getFileDescriptor(), startOffset, length);
mIsDataSourceSet = true;
prepare();
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
private void prepare() {
try {
mMediaPlayer.setOnVideoSizeChangedListener(
new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
mVideoWidth = width;
mVideoHeight = height;
updateTextureViewSize();
}
}
);
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mState = State.END;
log("Video has ended.");
if (mListener != null) {
mListener.onVideoEnd();
}
}
});
// don't forget to call MediaPlayer.prepareAsync() method when you use constructor for
// creating MediaPlayer
mMediaPlayer.prepareAsync();
// Play video when the media source is ready for playback.
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mIsVideoPrepared = true;
if (mIsPlayCalled && mIsViewAvailable) {
log("Player is prepared and play() was called.");
play();
}
if (mListener != null) {
mListener.onVideoPrepared();
}
}
});
} catch (IllegalArgumentException e) {
Log.d(TAG, e.getMessage());
} catch (SecurityException e) {
Log.d(TAG, e.getMessage());
} catch (IllegalStateException e) {
Log.d(TAG, e.toString());
}
}
/**
* Play or resume video. Video will be played as soon as view is available and media player is
* prepared.
*
* If video is stopped or ended and play() method was called, video will start over.
*/
public void play() {
if (!mIsDataSourceSet) {
log("play() was called but data source was not set.");
return;
}
mIsPlayCalled = true;
if (!mIsVideoPrepared) {
log("play() was called but video is not prepared yet, waiting.");
return;
}
if (!mIsViewAvailable) {
log("play() was called but view is not available yet, waiting.");
return;
}
if (mState == State.PLAY) {
log("play() was called but video is already playing.");
return;
}
if (mState == State.PAUSE) {
log("play() was called but video is paused, resuming.");
mState = State.PLAY;
mMediaPlayer.start();
return;
}
if (mState == State.END || mState == State.STOP) {
log("play() was called but video already ended, starting over.");
mState = State.PLAY;
mMediaPlayer.seekTo(0);
mMediaPlayer.start();
return;
}
mState = State.PLAY;
mMediaPlayer.start();
}
/**
* Pause video. If video is already paused, stopped or ended nothing will happen.
*/
public void pause() {
if (mState == State.PAUSE) {
log("pause() was called but video already paused.");
return;
}
if (mState == State.STOP) {
log("pause() was called but video already stopped.");
return;
}
if (mState == State.END) {
log("pause() was called but video already ended.");
return;
}
mState = State.PAUSE;
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
}
}
/**
* Stop video (pause and seek to beginning). If video is already stopped or ended nothing will
* happen.
*/
public void stop() {
if (mState == State.STOP) {
log("stop() was called but video already stopped.");
return;
}
if (mState == State.END) {
log("stop() was called but video already ended.");
return;
}
mState = State.STOP;
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
mMediaPlayer.seekTo(0);
}
}
/**
* @see MediaPlayer#setLooping(boolean)
*/
public void setLooping(boolean looping) {
mMediaPlayer.setLooping(looping);
}
/**
* @see MediaPlayer#seekTo(int)
*/
public void seekTo(int milliseconds) {
mMediaPlayer.seekTo(milliseconds);
}
/**
* @see MediaPlayer#getDuration()
*/
public int getDuration() {
return mMediaPlayer.getDuration();
}
static void log(String message) {
if (LOG_ON) {
Log.d(TAG, message);
}
}
private MediaPlayerListener mListener;
/**
* Listener trigger 'onVideoPrepared' and `onVideoEnd` events
*/
public void setListener(MediaPlayerListener listener) {
mListener = listener;
}
public interface MediaPlayerListener {
public void onVideoPrepared();
public void onVideoEnd();
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Surface surface = new Surface(surfaceTexture);
mMediaPlayer.setSurface(surface);
mIsViewAvailable = true;
if (mIsDataSourceSet && mIsPlayCalled && mIsVideoPrepared) {
log("View is available and play() was called.");
play();
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
그 후 MainActivity.java 에서 아래 코드와 같이이 클래스를 사용하십시오.
public class MainActivity extends AppCompatActivity implements View.OnClickListener,
ActionBar.OnNavigationListener {
// Video file url
private static final String FILE_URL = "http://techslides.com/demos/sample-videos/small.mp4";
private TextureVideoView mTextureVideoView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initActionBar();
if (!isWIFIOn(getBaseContext())) {
Toast.makeText(getBaseContext(), "You need internet connection to stream video",
Toast.LENGTH_LONG).show();
}
}
private void initActionBar() {
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
actionBar.setDisplayShowTitleEnabled(false);
SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list,
android.R.layout.simple_spinner_dropdown_item);
actionBar.setListNavigationCallbacks(mSpinnerAdapter, this);
}
private void initView() {
mTextureVideoView = (TextureVideoView) findViewById(R.id.cropTextureView);
findViewById(R.id.btnPlay).setOnClickListener(this);
findViewById(R.id.btnPause).setOnClickListener(this);
findViewById(R.id.btnStop).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnPlay:
mTextureVideoView.play();
break;
case R.id.btnPause:
mTextureVideoView.pause();
break;
case R.id.btnStop:
mTextureVideoView.stop();
break;
}
}
final int indexCropCenter = 0;
final int indexCropTop = 1;
final int indexCropBottom = 2;
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
switch (itemPosition) {
case indexCropCenter:
mTextureVideoView.stop();
mTextureVideoView.setScaleType(TextureVideoView.ScaleType.CENTER_CROP);
mTextureVideoView.setDataSource(FILE_URL);
mTextureVideoView.play();
break;
case indexCropTop:
mTextureVideoView.stop();
mTextureVideoView.setScaleType(TextureVideoView.ScaleType.TOP);
mTextureVideoView.setDataSource(FILE_URL);
mTextureVideoView.play();
break;
case indexCropBottom:
mTextureVideoView.stop();
mTextureVideoView.setScaleType(TextureVideoView.ScaleType.BOTTOM);
mTextureVideoView.setDataSource(FILE_URL);
mTextureVideoView.play();
break;
}
return true;
}
public static boolean isWIFIOn(Context context) {
ConnectivityManager connMgr =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
return (networkInfo != null && networkInfo.isConnected());
}
}
레이아웃 activity_main.xml 파일은 다음과 같습니다.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.example.videocropdemo.crop.TextureVideoView
android:id="@+id/cropTextureView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnPlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play" />
<Button
android:id="@+id/btnPause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause" />
<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop" />
</LinearLayout>
</RelativeLayout>
중앙 자르기에 대한 코드 출력 은 다음과 같습니다.
Glide 를 GIF 및 이미지로드에 사용할 수 있으며 원하는대로 크기 조정 옵션을 제공 할 수 있습니다. https://bumptech.github.io/glide/doc/targets.html#sizes-and-dimensions 및 https://futurestud.io/tutorials/glide-image-resizing-scaling 문서를 기반으로 합니다.
Glide v4에는 Android Ice Cream Sandwich (API 레벨 14) 이상이 필요합니다.
처럼 :
public static void loadCircularImageGlide(String imagePath, ImageView view) {
Glide.with(view.getContext())
.load(imagePath)
.asGif()
.override(600, 200) // resizes the image to these dimensions (in pixel). resize does not respect aspect ratio
.error(R.drawable.create_timeline_placeholder)
.fitCenter() // scaling options
.transform(new CircularTransformation(view.getContext())) // Even you can Give image tranformation too
.into(view);
}
'programing' 카테고리의 다른 글
Xcode Server 호스팅 저장소에서 봇을 만들 수 없습니다. (0) | 2020.12.02 |
---|---|
Xlib 및 Firefox 동작 (0) | 2020.12.02 |
기기 자동화 추적은 하나의 대상 연결 만 허용합니까? (0) | 2020.12.02 |
Camel을 사용하여 로컬 서비스에서 원격 서비스로 Rest 요청을 라우팅하는 방법 (0) | 2020.12.02 |
데이터를 CSV 플랫 파일로 내보내는 동안 포함 된 텍스트 한정자 문제를 해결하는 방법은 무엇입니까? (0) | 2020.12.02 |