반응형

구현

	private class NewHttpGetRequest extends AsyncTask<String, Void, String> {
		@Override
		protected String doInBackground(String... params){
			HttpsURLConnection urlConnection= null;
			java.net.URL url = null;
			String response = "";
			try {
				result=null;
				url  = new java.net.URL(params[0]);
				urlConnection=(HttpsURLConnection)url.openConnection();
				urlConnection.setRequestMethod("GET");
				urlConnection.setDoOutput(true);
				urlConnection.setDoInput(true);
				urlConnection.connect();

				int resCode = urlConnection.getResponseCode();
				Log.i(TAG,"## urlConnection.getResponseCode() :"+resCode);

				if (resCode == 200){
					InputStream inStream = null;
					inStream = urlConnection.getInputStream();
					BufferedReader bReader = new BufferedReader(new InputStreamReader(inStream));
					String temp = "";
					while((temp = bReader.readLine()) != null){
						response += temp;
					}
					bReader.close();
					inStream.close();
					result = response;
				} else {
					result = "";
				}

				urlConnection.disconnect();

				Log.i(TAG,"## response :" +response);
			} catch (Exception e) {
				e.printStackTrace();
				result = "";
			}
			return result;
		}

		@Override
		protected void onPostExecute(String s) {
			super.onPostExecute(s);
			//파일 없을 경우 처리
			if(result.equals("")){
				//파일 없을경우
			} else {
				//파일 있을경우
			}
		}
	}

사용

try {
	new NewHttpGetRequest().execute("your url").get();
} catch (ExecutionException e) {
	e.printStackTrace();
} catch (InterruptedException e) {
	e.printStackTrace();
}
반응형
반응형

 

import UIKit

class BottomSheetController: UIViewController {
    
    //MARK: - properties
    let bottomSheetTableViewID = "bottomSheetTableViewID"
    let otmealColor = UIColor(red: 240/255, green: 230/255, blue: 219/255, alpha: 1.0)
    
    //Bottom sheet
    var screenWidth:CGFloat = UIScreen.main.bounds.width
    var screenHeight:CGFloat = UIScreen.main.bounds.height
    
    var originY:CGFloat!
    var bsMimicHeightMin:CGFloat!
    var bsMimicHeightMax:CGFloat!
    var isOpen:Bool = false
    var conVC:UIViewController!
  
   //MARK: - init
    convenience init() {
        self.init(controller: nil , max: 0.0 , min:0.0)
    }
    init(controller:UIViewController?,max:CGFloat,min:CGFloat) {
        self.conVC = controller
        self.bsMimicHeightMin = min
        self.bsMimicHeightMax = max
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

     //MARK: - UI
    let containerView:UIView = {
        let v = UIView()
        v.backgroundColor = .white
        v.layer.cornerRadius = 5;
        v.layer.masksToBounds = true;
        
        v.layer.shadowColor = UIColor.black.cgColor
        v.layer.shadowOpacity = 0.8
        v.layer.shadowOffset = CGSize.zero
        v.layer.shadowRadius = 3
        v.layer.masksToBounds = false
        return v
    }()
    
    let pullView:UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .lightGray
        v.layer.cornerRadius = 5;
        v.layer.masksToBounds = true;
        return v
    }()
    
    var tableView : UITableView = {
       let tableview = UITableView()
        tableview.translatesAutoresizingMaskIntoConstraints = false
        tableview.backgroundColor = .lightGray
        tableview.separatorStyle = .singleLine
        tableview.bounces = false
        return tableview
    }()
     //MARK: - viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()
        initSelf()
        initContainer()
        initPullView()
        initTableview()
        

    }
    func initSelf(){
        view.backgroundColor = UIColor.black.withAlphaComponent(0.0)
    }
    
    func initContainer(){
        view.addSubview(containerView)
        containerView.frame = CGRect(x: 0, y: screenHeight-bsMimicHeightMin, width: screenWidth, height: bsMimicHeightMax)
        originY = containerView.frame.origin.y
        //action handler
         let tab = UIPanGestureRecognizer(target: self, action: #selector(pullViewRecognizer(pan:)))
         containerView.addGestureRecognizer(tab)
    }
    func initPullView(){
//        prepareBackgroundView(myView: pullView)
        containerView.addSubview(pullView)
        NSLayoutConstraint.activate([
            pullView.widthAnchor.constraint(equalToConstant: view.frame.size.width/3),
            pullView.heightAnchor.constraint(equalToConstant: 10),
            pullView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
            pullView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10)
        ])
    }
    func initTableview(){
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(MenuItemCell.self, forCellReuseIdentifier: bottomSheetTableViewID)
        
        containerView.addSubview(tableView)
        tableView.topAnchor.constraint(equalTo: pullView.bottomAnchor, constant: 30).isActive = true
        tableView.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: 0).isActive = true
        tableView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: 0).isActive = true
        tableView.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, constant: 0).isActive = true
    }
    
    
    func prepareBackgroundView(myView:UIView){
        let blurEffect = UIBlurEffect.init(style: .extraLight)
        let visualEffect = UIVisualEffectView.init(effect: blurEffect)
        let bluredView = UIVisualEffectView.init(effect: blurEffect)
        bluredView.contentView.addSubview(visualEffect)

        visualEffect.frame = myView.frame
        bluredView.frame = myView.frame
        myView.insertSubview(bluredView, at: 0)
    }
    
    //MARK: - action handler
    @objc func pullViewRecognizer(pan : UIPanGestureRecognizer){
        let translation = pan.translation(in: containerView)
        let velocity:CGFloat = pan.velocity(in: containerView).y
        
        if pan.state == .began {
             print("began \(translation.y)")
        }
        
        if  pan.state == .changed {
            if (isOpen){
                containerView.frame.origin.y = screenHeight-bsMimicHeightMax+translation.y
            } else {
                containerView.frame.origin.y = originY+translation.y
            }
        }
        
        if  pan.state == .ended {
            if (velocity<0){
                open()
            } else {
               close()
            }
        }
    }
    func open(){
        isOpen = true
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
            self.containerView.frame.origin.y = self.screenHeight-self.bsMimicHeightMax
            self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
        }, completion: nil)
    }
    func close(){
        print("close")
        isOpen = false
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
            self.containerView.frame.origin.y = self.originY
            self.view.backgroundColor = UIColor.black.withAlphaComponent(0.0)
        }, completion: nil)
    }
  
}

//MARK: - tableview delegate
extension BottomSheetController : UITableViewDelegate,UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cells = tableView.dequeueReusableCell(withIdentifier: bottomSheetTableViewID, for: indexPath)
        cells.backgroundColor = .white
        return cells
    }
    
    //cell height
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        close()
    }
    
}
반응형
반응형

기존 AppDelegate에서 이런식으로 사용함.

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = MainViewController()
        window?.makeKeyAndVisible()
        return true
    }

xcode11 에서 멀티윈도우를 지원하기위해 SceneDelegate를 추가했다.

기존 AppDelegate에서 추가하려니 안되서 찾아보니 아래와같이 window.windowScene에 scene을 추가해주면된다.

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let scene = (scene as? UIWindowScene) else { return }
        
        
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = MainViewController()
        window?.windowScene = scene
        window?.makeKeyAndVisible()
    }
반응형

'IOS' 카테고리의 다른 글

(IOS)sms로 인증문자 받아서 keyboard위에 띄우기  (0) 2020.01.30
(IOS) 토스트 만들기 - objective-c  (0) 2020.01.22
(IOS)스토리보드에서 VIEW 관리하기  (0) 2020.01.21
(IOS)날짜 비교  (0) 2020.01.14
[IOS]custom bottom sheet  (0) 2019.12.31
반응형

 

백그라운드 상태를 확인하는방법 1

 

1. Application class 를 상속 받고 ActivityLicycleCallback을 등록해준다.

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        setupLifecycleObserver();
    }
    
    private void setupAppLifeCycleTracker(){
       registerActivityLifecycleCallbacks(new AppLifeCycleTracker());
    }
 }

 

2. 등록한 started , stopped method에 숫자를 추가해 자신의 액티비티 상황에 맞춰 숫자를 활용해준다.

  /*********************************
     inner class AppLifeCycleTracker
     **********************************/
    class AppLifeCycleTracker implements ActivityLifecycleCallbacks {

		public int numStarted = 0;

        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

        }

        @Override
        public void onActivityStarted(Activity activity) {
            if (numStarted == 0){
                //went to foreground
                
            }
            numStarted++;
        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {
            numStarted--;
            if (numStarted == 0){
                //went to background
               
            }

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {

        }
    }// end AppLifeCycleTracker

 

3. manifest - > application - > name에 클래스명 넣기.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name=".App">
    ....    
           </application>
....
</manifest>

 

백그라운드 상태를 확인하는법 2

 

1. gradle에 lifecycle 라이브러리들을 추가해준다.

dependencies {
	...
    //lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime:2.0.0"
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
    ...
}

2. Application class 를 상속 받고 LifeCycleObserver를 추가해준다.

public class App extends Application {

	private LifecycleObserver lifecycleListener = new CycleListener();
    
    @Override
    public void onCreate() {
        super.onCreate();
        setupLifecycleObserver();
    }
    
    private void setupLifecycleObserver(){
        ProcessLifecycleOwner.get().getLifecycle().addObserver(lifecycleListener);
    }
 }

3. LifeCycleObserver는 아래와 같으며 @OnLicycleEvent 에노테이션으로 현재 상태를 나타내준다.

    class CycleListener implements LifecycleObserver{
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        public void onMoveToFoground(){
       		 // Moving to Foground…
           
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        public void onMoveToBackground() {
			// Moving to background…
        }
    } // end LifecycleObserver

 

 

 

추가로 ..

안드로이드의 LifeCycleObserver.

    class CycleListener implements LifecycleObserver{
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        public void onMoveToFoground(){
       		 // Moving to Foground…
           
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        public void onMoveToBackground() {
			// Moving to background…
        }
    } // end LifecycleObserver

IOS의 AppDelegate func.

func applicationDidEnterBackground(_ application: UIApplication) {

}


func applicationWillEnterForeground(_ application: UIApplication) {

}    

위 두개의 코드와 같이 안드로이드도 백그라운드 관리를 사용자 임의대로 설정하는 것이 아닌 점점 앱 메스드를 만들어 제한하고 메모리관리에 신경쓰는 부분이 아닐까 생각이든다.

 

점점 ios의 장점을 따라가려고 하는것같다. flutter때문인가 .. ?

반응형
반응형

 

 

 

webview settings 관련


// 자바 스크립트 사용
webSettings.setjavaScriptEnabled(true);



// mixed content 처리 여부

webSettings.setMixedContentMode(int)

MIXED_CONTENT_ALWAYS_ALLOW

MIXED_CONTENT_COMPATIBILITY_MODE

MIXED_CONTENT_NEVER_ALLOW



// DOM Storage Api 허용 여부

webSettings.setDomStorageEnabled(true);

이 부분이 중요한게 특정 사이트의 경우 메뉴 버튼을 이 API 를 사용하는 경우가 있어 이것을 허용해주지 않으면 버튼을 눌렀는데 반응이 없는 경우가 있다. 

예시 > rakuten.co.jp



// 기본 인코딩 설정

webSettings.setDefaultTextEncodingName("UTF-8");

// 웹 뷰에 맞게 출력

webSettings.setLoadWithOverviewMode(true);

webSettings.setUseWideViewPort(true);



// 플러그인 사용

webSettings.setPluginState(PluginState.ON);



// 화면 줌 컨트롤과 제스처를 사용하여 확대

webSettings.setSupportZoom(true);



// 내장 줌 컨트롤 사용

webSettings.setBuildInZoomControls(true);

// 내장 줌 컨트롤 표시 여부

webSettings.setDisplayZoomControls(false);



// 앱 캐시 사용 여부 설정

webSettings.setAppCacheEnabled(true);

// 앱 캐시 크기 설정

webSettings.setAppCacheMaxsize(1024*1024*8);

// 캐시 파일 경로 설정

webSettings.setAppCachePath(path);

// 캐시 방식 설정

webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);



// 암호 저장

webSettings.setSavepassword(true);



// 양식 데이터 저장

webSettings.setSaveFormData(true);



// 웹 뷰 내에서 파일 액세스 활성화

webSettings.setAllowFileAccess(true); API 3

파일에 접근하는 것을 허용

webSettings.getAllowFileAccessFromFileURLs(true); API 16

파일 구성표 URL의 컨텍스트에서 실행중인 JavaScript가 다른 파일 구성표 URL의 콘텐츠에 액세스 할 수 있는지 여부를 가져옵니다.

webSettings.getAllowUniversalAccessFromFileURLS(true); API 16

파일 구성표 URL의 컨텍스트에서 실행되는 JavaScript가 모든 출처의 콘텐츠에 액세스 할 수 있는지 여부를 가져옵니다. 여기에는 다른 파일 구성표 URL의 내용에 대한 액세스가 포함됩니다.



// 마우스 오버를 활성화 하기 위해 Light Touch를 사용 여부 설정

webSettings.setLighttouchEnabled(true);

출처: https://wangear.tistory.com/entry/Android-WebView-설정의-모든것 [wang&joo story]

 

반응형
반응형

아침에 출근하자마다 cs폭탄이 떨어졌다. 노트8기종에서 특정동작을하면 앱이 바로 죽는 현상이 나타났다는 것이다.

한참을 고민하다가 최근 리뉴얼때문에 minSDK버전을 올렷던 부분을 의심했는데 역시나 ...

 

참고이미지 : 나무위키

이놈은 4.1 ~ 5.0.1 까지 지원하는 겁나 옛날폰이엿던것이다 ... 이러니 에러가 나지 ... ㅋㅋㅋ

 

 

 

참고이미지 : 구글공홈

minsdk버전을 올려주고 , os 버전과 대조해보면 16 ~ 21 레벨의 예외처리를 해주면 되는 부분인것같다. 기본적인 버전 예외처리도 안했다니 .. 난아직 멀은것같다.

반응형
반응형

예전에 간단히 이벤트 팝업만들었을때 생각나서 올려보려고한다. 

 

서버에 html파일 있는지 확인 -> 있으면 가저와서 로딩후 리사이징

	//이벤트 팝업 변수들
	private static final String CHECK_USER_DATE = "FIRST_MEET";
	private static final String TAG = "MainActivity";
	private static final String CHECK_EVNET_POPUP = "CHECK_EVNET_POPUP";
	private static final String CHECK_EVNET_POPUP_NO = "CHECK_EVNET_POPUP_NO";
	private static final String CHECK_EVNET_POPUP_YES = "CHECK_EVNET_POPUP_YES";
	private String event_popup_result;

-지금와서 보니 preference 키값들은 별도 클래스에 모아서 관리하는게 좋은것같다.

 

// EVENT POP
	public void checkShowOrNotshow(){
		String chk_today =null;
		String pref_user_date= null;

		SharedPreferences pref = getApplicationContext().getSharedPreferences("event_popup", MODE_PRIVATE);
		chk_today = pref.getString(CHECK_EVNET_POPUP,CHECK_EVNET_POPUP_NO);
		pref_user_date = pref.getString(CHECK_USER_DATE,"FIRST_MEET");

		if(!pref_user_date.equals("FIRST_MEET")){
			int my_result =compareUserdateWithToday(pref_user_date);
			//오늘 접속함
			if(my_result == 0){
				Log.i(TAG,"## 이벤트 팝업 : 오늘 접속함");

				//하루 안보기 클릭
				if(chk_today.equals(CHECK_EVNET_POPUP_YES)){
					Log.i(TAG,"## 이벤트 팝업 : 하루 안보기 클릭");
				}
				//하루 안보기 클릭 안했음
				else if(chk_today.equals(CHECK_EVNET_POPUP_NO)){
					Log.i(TAG,"## 이벤트 팝업 : 하루 안보기 클릭 안했음");
					showEventPopup();
				}
			}
			//어제 접속함
			else if(my_result < 0){
				Log.i(TAG,"## 이벤트 팝업 : 어제 접속함");
				getApplication().getSharedPreferences("event_popup",MODE_PRIVATE).edit().putString(CHECK_EVNET_POPUP,CHECK_EVNET_POPUP_NO).apply();
				pref.edit().putString(CHECK_USER_DATE,getToday()).apply();
				showEventPopup();
			}
			//미래에서 왔을일은 없으니 에러
			else{
				Log.i(TAG,"## 이벤트 팝업 : 미래에서 왔을일은 없으니 에러");
				getApplication().getSharedPreferences("event_popup",MODE_PRIVATE).edit().putString(CHECK_EVNET_POPUP,CHECK_EVNET_POPUP_NO).apply();
			}
		} else {
			Log.i(TAG,"## 이벤트 팝업 : 앱을 처음 깔음");
			pref.edit().putString(CHECK_USER_DATE,getToday()).apply();
			showEventPopup();
		}

	}

	private void showEventPopup(){
		Intent intent = new Intent(MainActivity.this,EventPopupActivity.class);
		intent.putExtra("URL",URL.EVENT_POPUP.url(false));
		startActivity(intent);
		overridePendingTransition(R.anim.activity_fade_in,0);
	}

	// 미래 : int_calDateDays > 0 양수
	// 현재 : int_calDateDays == 0 같을때
	// 과거 : int_calDateDays < 0 음수
	private int compareUserdateWithToday(String user_date) {
		Integer int_calDateDays =null;
		try{
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
			Calendar cal = Calendar.getInstance();

			Date FirstDate = format.parse(user_date);
			Date SecondDate = format.parse(getToday());// 오늘 날짜를 yyyy-MM-dd포맷 String 값

			long calDate = FirstDate.getTime() - SecondDate.getTime();
			Long calDateDays = calDate / ( 24*60*60*1000);
			int_calDateDays = (calDateDays != null) ? calDateDays.intValue() : -1;//long to int
		}
		catch(ParseException e) { e.printStackTrace(); }
		return int_calDateDays;
	}


	private static String getToday() {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
		final Calendar cal = Calendar.getInstance();
		return format.format(cal.getTime());
	}

	// start NewHttpGetRequest.class
	private class NewHttpGetRequest extends AsyncTask<String, Void, String> {
		@Override
		protected String doInBackground(String... params){
			HttpsURLConnection urlConnection= null;
			java.net.URL url = null;
			String response = "";
			try {
				event_popup_result=null;
				url  = new java.net.URL(params[0]);
				urlConnection=(HttpsURLConnection)url.openConnection();
				urlConnection.setRequestMethod("GET");
				urlConnection.setDoOutput(true);
				urlConnection.setDoInput(true);
				urlConnection.connect();
				InputStream inStream = null;
				inStream = urlConnection.getInputStream();
				BufferedReader bReader = new BufferedReader(new InputStreamReader(inStream));
				String temp = "";
				while((temp = bReader.readLine()) != null){
					//Parse data
					response += temp;
				}
				bReader.close();
				inStream.close();
				urlConnection.disconnect();
				event_popup_result = response;
				Log.i("??","## response :" +response);
			} catch (FileNotFoundException no_file_exception){
				event_popup_result ="FileNotFoundException";
			} catch (Exception e) {
				e.printStackTrace();
				Log.i("??","## Exception :" +e.toString());
				event_popup_result = "otherException";
			}
			return event_popup_result;
		}

		@Override
		protected void onPostExecute(String s) {
			super.onPostExecute(s);
			//파일 없을 경우 처리
			if(event_popup_result.contains("FileNotFoundException")){
				Toast.makeText(MainActivity.this,event_popup_result, Toast.LENGTH_SHORT).show();
			}
			//다른 익셉션일 경우 처리
			else if(event_popup_result.contains("otherException")){
				Toast.makeText(MainActivity.this,event_popup_result, Toast.LENGTH_SHORT).show();
			}
			// 통신이 성공했을때
			else{
				checkShowOrNotshow();
			}

			Log.i("??","## onPostExecute" +s);
		}
	}// end NewHttpGetRequest.class

- 통신에 성공하고나면 ,checkShowOrNotShow()를 불러 오늘하루안보기를 클릭했는지 체크한다.

 

public class EventPopupActivity extends AppCompatActivity {
    private static final String TAG = "EventPopupActivity";
    private static final String CHECK_EVNET_POPUP = "CHECK_EVNET_POPUP";
    private static final String CHECK_EVNET_POPUP_YES = "CHECK_EVNET_POPUP_YES";

    private LinearLayout line_container;
    private Button pop_close_button;
    private Button not_show_today_btn;
    private WebView pop_webview;

    View.OnClickListener pop_close_button_lisn = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            finish();
            overridePendingTransition(0, R.anim.activity_fade_out);//종료 애니메이션
        }
    };
    View.OnClickListener not_show_today_btn_lisn = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            getApplication().getSharedPreferences("event_popup",MODE_PRIVATE).edit().putString(CHECK_EVNET_POPUP,CHECK_EVNET_POPUP_YES).apply();
            finish();
            overridePendingTransition(0, R.anim.activity_fade_out);//종료 애니메이션
        }
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        Locale locale = DeviceUtil.getDeviceLocale(this);
        Configuration config = getResources().getConfiguration();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            config.setLocale(locale);
            createConfigurationContext(config);
        } else {
            config.locale = locale;
        }
        getResources().updateConfiguration(config, getResources().getDisplayMetrics());

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_popup_1);
        settingViews();//view 세팅
        webviewSettings();//webview 기본 세팅

        String getURL = null;
        getURL = getIntent().getStringExtra("URL");
        ELog.i(TAG, getURL);

        pop_webview.loadUrl(EventUtil.makeCountryURL(this,getURL));
    }

    //findByView
    private void settingViews() {
        line_container = findViewById(R.id.line_container);
        pop_close_button = findViewById(R.id.pop_close_button);
        not_show_today_btn = findViewById(R.id.not_show_today_btn);

        pop_close_button.setOnClickListener(pop_close_button_lisn);
        not_show_today_btn.setOnClickListener(not_show_today_btn_lisn);
        pop_webview = findViewById(R.id.pop_webview);
    }

    //webview setting
    private void webviewSettings() {
        pop_webview.clearCache(true);
        pop_webview.setWebViewClient(new SimpleWebviewClient());
        pop_webview.addJavascriptInterface(new WebAppInterface(),"AndroidFunction");
        WebSettings set = pop_webview.getSettings();
        set.setJavaScriptEnabled(true);
        set.setJavaScriptCanOpenWindowsAutomatically(true);
        set.setDomStorageEnabled(true);
        set.setCacheMode(WebSettings.LOAD_NO_CACHE);
    }

    @Override
    public void finish() {
        super.finish();

    }

    class SimpleWebviewClient extends WebViewClient {
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            ELog.i(TAG, "## onPageStarted : " + url);
            if (url.contains("/popup/event_detail")) {
                view.stopLoading();
                Intent intent = new Intent(EventPopupActivity.this, EventPopupDetailActivity.class);
                startActivity(intent);
                overridePendingTransition(R.anim.activity_right_enter, R.anim.activity_right_out);
                finish();
            }
            super.onPageStarted(view, url, favicon);
        }
        @Override
        public void onPageFinished(WebView view, String url) {
            ELog.i(TAG, "## onPageFinished : " + url);
            view.loadUrl("javascript:AndroidFunction.resize(document.body.scrollHeight)");
        }
    }
    //name : AndroidFunction
    public class WebAppInterface {
        @JavascriptInterface
        public void resize(final float height) {
            //webview 안쪽 컨텐츠 사이즈에 따른 리사이징
            float webViewHeight = (height * getResources().getDisplayMetrics().density);
            ViewGroup.LayoutParams params = line_container.getLayoutParams();//컨테이너
            ViewGroup.LayoutParams btn_params = not_show_today_btn.getLayoutParams();//버튼
            params.height =  btn_params.height+((int)Math.round(webViewHeight));//컨테이너+버튼
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    line_container.setLayoutParams(params);//리사이징 : UIException이 나기 때문에 쓰레드로 구현
                }
            });

        }
    }
}

- 통신했을땐 파일이 있었으니까 바로 웹뷰로 html을 불러준다.

- 이후 resize 를 호출해 사이즈딱맞게 재정렬한다.

 

지금보니까 소스가 개판...ㅋㅋㅋ .. 가끔씩 예전소스보면서 리뷰하는것도 좋은것같네요

반응형
반응형

 

 

옥션이나 지마켓을 보면 쇼핑몰들은 다이나믹한 레이아웃을 사용해 사용자들의 시선을 끄는 어플이 대세인것 같다.

최근에 사업팀이 이쪽에 꼿혀서 만들어달라고 제안이 들어왔는데 그때 해본걸 정리하려고한다.

 

 

상단동작

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    android:animateLayoutChanges="true">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/top_app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
        android:background="@color/colorPrimary"
        app:elevation="0dp">
        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways|snap">

            <androidx.appcompat.widget.Toolbar
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize" />
        </com.google.android.material.appbar.CollapsingToolbarLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="75dp"
            android:background="@color/dark_gray"/>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/main_recycle_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
        app:layout_anchor="@id/main_bottom_navbar"
        app:layout_anchorGravity="top" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/main_bottom_navbar"
        android:layout_width="match_parent"
        android:layout_height="54dp"
        android:layout_gravity="bottom"
        app:labelVisibilityMode="labeled"
         app:itemIconTint="@color/bottom_nav_color"
        app:itemTextColor="@color/bottom_nav_color"
        app:menu="@menu/bottom_nav_bar" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

- 구글이 빠른 개발속도를 위해 편리한 위젯을 많이 만들어 놓고있다 그중에서도 가장많이 쓰이는것이 AppbarLayout,collapsingToolbarLayout,Toolbar조합일 것이다.

 

- 꼭 coordinatorLayout안쪽에 구성을 해야한다. 그이유는 리사이클뷰에

app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"

layout_behavior 속성을 사용하기위해서이다.

 

- 눈여겨 봐야할 부분은 첫번째로 collapsingToolbarLayout에

app:layout_scrollFlags="scroll|enterAlways|snap"

이부분이다. 레이아웃이 반쯤 들어가면 자동으로 사라지고 아니면 자동으로 이전 레이아웃으로 복구가된다.

 

-두번째는 recyclerView에 

app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"

이부분이다 . 이부분의 터치에따라 appbar와 bottomNavigationbar가 보여지고 사라진다.

 

하단동작

public class TopNavbarBehavior extends CoordinatorLayout.Behavior<View> {
    private static final String TAG = "BottomNavBehavior";
    private float originHeight;
    private Context mContext;
    private Activity mActivity;
    private float endTransitionY;

    public TopNavbarBehavior() {
    }

    public TopNavbarBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }


    public TopNavbarBehavior(Context context){
        this.mContext = context;
    }


    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        originHeight = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getMeasuredHeight()));
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//수직일때 작동
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //Behavior을 가진 View(child)
        //setTranslationY: 상단 위치를 기준으로 수직 위치를 설정합니다.
        //getTranslationY: 상단 위치를 기준으로 하고 Layout에 배치된 위치에 추가하여 개체의 위치를 효과적으로 지정할 수 있습니다.
        //dyConsumed: 타겟의 스크롤 조작으로 인해 소비된 수직 픽셀
        float transitionY = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getTranslationY() + dy));
        child.setTranslationY(transitionY);
        endTransitionY = transitionY;
    }

    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {

        //반이거나 반이상이면 숨기기
        if ((originHeight/2) >= endTransitionY ) {
            child.setTranslationY(0);
        }
        //반이하면 보이기
        else if ((originHeight/2) < endTransitionY ){
            child.setTranslationY(originHeight);
        }
    }
}

-coordinatorLayout을 사용하는 이유가 여기서 하나더있다. 

 

CoordinatorLayout.Behavior<View>

이녀석을 상속받기위해 사용한다. 

 

 

 

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        originHeight = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getMeasuredHeight()));
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//수직일때 작동
    }

- 현재위치를 초기화시켜주고, 방향을 결정한다.

axes == 이부분은 onNestedPreScroll,onStopNestedScroll을 작동시킬껀지 결정해주는 부분이다.

 

 

 

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //Behavior을 가진 View(child)
        //setTranslationY: 상단 위치를 기준으로 수직 위치를 설정합니다.
        //getTranslationY: 상단 위치를 기준으로 하고 Layout에 배치된 위치에 추가하여 개체의 위치를 효과적으로 지정할 수 있습니다.
        //dyConsumed: 타겟의 스크롤 조작으로 인해 소비된 수직 픽셀
        float transitionY = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getTranslationY() + dy));
        child.setTranslationY(transitionY);
        endTransitionY = transitionY;
    }

- 솔직히 수학을 못해서 무슨원리로 작동하는지는 이해가 안되지만 위에 주석을 보고 어렴풋히 알것도 같다.

 

 

    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {

        //반이거나 반이상이면 숨기기
        if ((originHeight/2) >= endTransitionY ) {
            child.setTranslationY(0);
        }
        //반이하면 보이기
        else if ((originHeight/2) < endTransitionY ){
            child.setTranslationY(originHeight);
        }
    }

- 상단동작부분에

app:layout_scrollFlags="scroll|enterAlways|snap"

이런 부분이 있엇을것이다. 위에서 설명했듯이 손가락으로 드래그해서 사라진 레이아웃이 반이상이면 부드럽게 사라지고 아니면 보이게되는 부분인데 아래쪽도 아주 똑같지는 않지만 비슷하게 동작하도록 계산을해서 보이고 숨기고를 구성해보았다.

 

 

 

 

조립하기

public class MainActivity extends AppCompatActivity {

    BottomNavigationView bottomNavbar;
    AppBarLayout toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //리사클뷰
        RecyclerView rv =  findViewById(R.id.main_recycle_view);
        MainRecyclerview adapter = new MainRecyclerview(this);
        LinearLayoutManager lm = new LinearLayoutManager(this);
        rv.setLayoutManager(lm);
        rv.setAdapter(adapter);

        //하단 네비게이션바
        bottomNavbar = findViewById(R.id.main_bottom_navbar);
        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) bottomNavbar.getLayoutParams();
        BottomNavBehavior bnb = new BottomNavBehavior(this);
        params.setBehavior(bnb);

    }
}

 

스크린샷은 추후에 업로드

 

언제나 고수분들 태클은 환영입니다. 

 

감사합니다.

 

반응형

+ Recent posts